[automerger skipped] Merge "Import translations. DO NOT MERGE ANYWHERE" into 24D1-dev am: e141355839 -s ours
am skip reason: contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/28040916
Change-Id: Ic5213145bd1a016d9af140a2c475ce4e12880cd9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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 edeb0b3..0000000
--- a/Cronet/tests/common/Android.bp
+++ /dev/null
@@ -1,48 +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",
- ],
- 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 bded8fb..0000000
--- a/Cronet/tests/common/AndroidTest.xml
+++ /dev/null
@@ -1,62 +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" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testSetLibraryLoaderIsIgnoredInNativeCronetEngineBuilderImpl" />
- <!-- b/316571753 -->
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testBaseFeatureFlagsOverridesEnabled" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testHttpFlagsAppliedIfAppIdMatches" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testHttpFlagsAreLoaded" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testSetLibraryLoaderIsEnforcedByDefaultEmbeddedProvider" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testHttpFlagsAppliedIfAtMinVersion" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testHttpFlagsAppliedIfAboveMinVersion" />
- <!-- b/316567693 -->
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestTest#testSSLCertificateError" />
- <!-- 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="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/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 bccbe29..0000000
--- a/Cronet/tests/mts/AndroidTest.xml
+++ /dev/null
@@ -1,65 +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" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testSetLibraryLoaderIsIgnoredInNativeCronetEngineBuilderImpl" />
- <!-- b/316571753 -->
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testBaseFeatureFlagsOverridesEnabled" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testHttpFlagsAppliedIfAppIdMatches" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testHttpFlagsAreLoaded" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testSetLibraryLoaderIsEnforcedByDefaultEmbeddedProvider" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testHttpFlagsAppliedIfAtMinVersion" />
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testHttpFlagsAppliedIfAboveMinVersion" />
- <!-- b/316567693 -->
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestTest#testSSLCertificateError" />
- <!-- 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"/>
- </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/Cronet/tools/import/copy.bara.sky b/Cronet/tools/import/copy.bara.sky
deleted file mode 100644
index 61e3ba4..0000000
--- a/Cronet/tools/import/copy.bara.sky
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright 2023 Google Inc. All rights reserved.
-#
-# 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.
-
-common_excludes = [
- # Exclude all Android build files
- "**/Android.bp",
- "**/Android.mk",
-
- # Exclude existing *OWNERS files
- "**/*OWNERS",
- "**/.git/**",
- "**/.gitignore",
-]
-
-cronet_origin_files = glob(
- include = [
- "base/**",
- "build/**",
- "build/buildflag.h",
- "chrome/VERSION",
- "components/cronet/**",
- "components/metrics/**",
- "components/nacl/**",
- "components/prefs/**",
- "crypto/**",
- "ipc/**",
- "net/**",
- # Note: Only used for tests.
- "testing/**",
- "url/**",
- "LICENSE",
- ],
- exclude = common_excludes + [
- # Per aosp/2367109
- "build/android/CheckInstallApk-debug.apk",
- "build/android/unused_resources/**",
- "build/linux/**",
-
- # Per aosp/2374766
- "components/cronet/ios/**",
- "components/cronet/native/**",
-
- # Per aosp/2399270
- "testing/buildbot/**",
-
- # Exclude all third-party directories. Those are specified explicitly
- # below, so no dependency can accidentally creep in.
- "**/third_party/**",
- ],
-) + glob(
- # Explicitly include third-party dependencies.
- # Note: some third-party dependencies include a third_party folder within
- # them. So far, this has not become a problem.
- include = [
- "base/third_party/cityhash/**",
- "base/third_party/cityhash_v103/**",
- "base/third_party/double_conversion/**",
- "base/third_party/dynamic_annotations/**",
- "base/third_party/icu/**",
- "base/third_party/nspr/**",
- "base/third_party/superfasthash/**",
- "base/third_party/valgrind/**",
- # Those are temporarily needed until Chromium finish the migration
- # of libc++[abi]
- "buildtools/third_party/libc++/**",
- "buildtools/third_party/libc++abi/**",
- # Note: Only used for tests.
- "net/third_party/nist-pkits/**",
- "net/third_party/quiche/**",
- "net/third_party/uri_template/**",
- "third_party/abseil-cpp/**",
- "third_party/android_ndk/sources/android/cpufeatures/**",
- "third_party/ashmem/**",
- "third_party/boringssl/**",
- "third_party/brotli/**",
- # Note: Only used for tests.
- "third_party/ced/**",
- "third_party/cpu_features/**",
- # Note: Only used for tests.
- "third_party/google_benchmark/**",
- # Note: Only used for tests.
- "third_party/googletest/**",
- "third_party/icu/**",
- "third_party/jni_zero/**",
- "third_party/libc++/**",
- "third_party/libc++abi/**",
- "third_party/libevent/**",
- # Note: Only used for tests.
- "third_party/libxml/**",
- # Note: Only used for tests.
- "third_party/lss/**",
- "third_party/metrics_proto/**",
- "third_party/modp_b64/**",
- "third_party/protobuf/**",
- # Note: Only used for tests.
- "third_party/quic_trace/**",
- # Note: Cronet currently uses Android's zlib
- # "third_party/zlib/**",
- "url/third_party/mozilla/**",
- ],
- exclude = common_excludes,
-)
-
-core.workflow(
- name = "import_cronet",
- authoring = authoring.overwrite("Cronet Mainline Eng <cronet-mainline-eng+copybara@google.com>"),
- # Origin folder is specified via source_ref argument, see import_cronet.sh
- origin = folder.origin(),
- origin_files = cronet_origin_files,
- destination = git.destination(
- # The destination URL is set by the invoking script.
- url = "overwritten/by/script",
- push = "upstream-import",
- ),
- mode = "SQUASH",
-)
diff --git a/Cronet/tools/import/import_cronet.sh b/Cronet/tools/import/import_cronet.sh
deleted file mode 100755
index 0f04af7..0000000
--- a/Cronet/tools/import/import_cronet.sh
+++ /dev/null
@@ -1,146 +0,0 @@
-#!/bin/bash
-
-# Copyright 2023 Google Inc. All rights reserved.
-#
-# 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.
-
-# Script to invoke copybara locally to import Cronet into Android.
-# Inputs:
-# Environment:
-# ANDROID_BUILD_TOP: path the root of the current Android directory.
-# Arguments:
-# -l rev: The last revision that was imported.
-# Optional Arguments:
-# -n rev: The new revision to import.
-# -f: Force copybara to ignore a failure to find the last imported revision.
-
-set -e -x
-
-OPTSTRING=fl:n:
-
-usage() {
- cat <<EOF
-Usage: import_cronet.sh -n new-rev [-l last-rev] [-f]
-EOF
- exit 1
-}
-
-COPYBARA_FOLDER_ORIGIN="/tmp/copybara-origin"
-
-#######################################
-# Create local upstream-import branch in external/cronet.
-# Globals:
-# ANDROID_BUILD_TOP
-# Arguments:
-# none
-#######################################
-setup_upstream_import_branch() {
- local git_dir="${ANDROID_BUILD_TOP}/external/cronet"
-
- (cd "${git_dir}" && git fetch aosp upstream-import:upstream-import)
-}
-
-#######################################
-# Setup folder.origin for copybara inside /tmp
-# Globals:
-# COPYBARA_FOLDER_ORIGIN
-# Arguments:
-# new_rev, string
-#######################################
-setup_folder_origin() (
- local _new_rev=$1
- mkdir -p "${COPYBARA_FOLDER_ORIGIN}"
- cd "${COPYBARA_FOLDER_ORIGIN}"
-
- if [ -d src ]; then
- (cd src && git fetch --tags && git checkout "${_new_rev}")
- else
- # For this to work _new_rev must be a branch or a tag.
- git clone --depth=1 --branch "${_new_rev}" https://chromium.googlesource.com/chromium/src.git
- fi
-
-
- cat <<EOF >.gclient
-solutions = [
- {
- "name": "src",
- "url": "https://chromium.googlesource.com/chromium/src.git",
- "managed": False,
- "custom_deps": {},
- "custom_vars": {},
- },
-]
-target_os = ["android"]
-EOF
- cd src
- # Set appropriate gclient flags to speed up syncing.
- gclient sync \
- --no-history \
- --shallow \
- --delete_unversioned_trees
-)
-
-#######################################
-# Runs the copybara import of Chromium
-# Globals:
-# ANDROID_BUILD_TOP
-# COPYBARA_FOLDER_ORIGIN
-# Arguments:
-# last_rev, string or empty
-# force, string or empty
-#######################################
-do_run_copybara() {
- local _last_rev=$1
- local _force=$2
-
- local -a flags
- flags+=(--git-destination-url="file://${ANDROID_BUILD_TOP}/external/cronet")
- flags+=(--repo-timeout 3m)
-
- # buildtools/third_party/libc++ contains an invalid symlink
- flags+=(--folder-origin-ignore-invalid-symlinks)
- flags+=(--git-no-verify)
-
- if [ ! -z "${_force}" ]; then
- flags+=(--force)
- fi
-
- if [ ! -z "${_last_rev}" ]; then
- flags+=(--last-rev "${_last_rev}")
- fi
-
- /google/bin/releases/copybara/public/copybara/copybara \
- "${flags[@]}" \
- "${ANDROID_BUILD_TOP}/packages/modules/Connectivity/Cronet/tools/import/copy.bara.sky" \
- import_cronet "${COPYBARA_FOLDER_ORIGIN}/src"
-}
-
-while getopts $OPTSTRING opt; do
- case "${opt}" in
- f) force=true ;;
- l) last_rev="${OPTARG}" ;;
- n) new_rev="${OPTARG}" ;;
- ?) usage ;;
- *) echo "'${opt}' '${OPTARG}'"
- esac
-done
-
-if [ -z "${new_rev}" ]; then
- echo "-n argument required"
- usage
-fi
-
-setup_upstream_import_branch
-setup_folder_origin "${new_rev}"
-do_run_copybara "${last_rev}" "${force}"
-
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/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index 7612210..9e4e4a1 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -1,9 +1,12 @@
lorenzo@google.com
satk@google.com #{LAST_RESORT_SUGGESTION}
-# For cherry-picks of CLs that are already merged in aosp/master, or flaky test fixes.
+# For cherry-picks of CLs that are already merged in aosp/master, flaky test
+# fixes, or no-op refactors.
jchalard@google.com #{LAST_RESORT_SUGGESTION}
+# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
+# APF firmware tests (to verify correct behaviour of the wifi APF interpreter)
maze@google.com #{LAST_RESORT_SUGGESTION}
-# In addition to cherry-picks and flaky test fixes, also for incremental changes on NsdManager tests
-# to increase coverage for existing behavior, and testing of bug fixes in NsdManager
+# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
+# NsdManager tests
reminv@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ab3ed66..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": [
@@ -246,6 +366,9 @@
},
{
"exclude-annotation": "com.android.testutils.DnsResolverModuleTest"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
}
]
},
@@ -270,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": [
{
@@ -329,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 e4e6c70..e84573b 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -74,6 +74,8 @@
"net-utils-device-common-bpf",
"net-utils-device-common-ip",
"net-utils-device-common-netlink",
+ "net-utils-device-common-struct",
+ "net-utils-device-common-struct-base",
"netd-client",
"tetheringstatsprotos",
],
@@ -98,7 +100,6 @@
],
static_libs: [
"NetworkStackApiCurrentShims",
- "net-utils-device-common-struct",
],
apex_available: ["com.android.tethering"],
lint: {
@@ -115,7 +116,6 @@
],
static_libs: [
"NetworkStackApiStableShims",
- "net-utils-device-common-struct",
],
apex_available: ["com.android.tethering"],
lint: {
@@ -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 30bdf37..8ed5ac0 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -103,16 +103,17 @@
"dscpPolicy.o",
"netd.o",
"offload.o",
- "offload@btf.o",
+ "offload@mainline.o",
"test.o",
- "test@btf.o",
+ "test@mainline.o",
],
apps: [
"ServiceConnectivityResources",
],
prebuilts: [
"current_sdkinfo",
- "netbpfload.mainline.rc",
+ "netbpfload.33rc",
+ "netbpfload.35rc",
"ot-daemon.init.34rc",
],
manifest: "manifest.json",
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index a287b42..cccafd5 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -47,6 +47,7 @@
field public static final int TETHERING_INVALID = -1; // 0xffffffff
field public static final int TETHERING_NCM = 4; // 0x4
field public static final int TETHERING_USB = 1; // 0x1
+ field @FlaggedApi("com.android.net.flags.tethering_request_virtual") public static final int TETHERING_VIRTUAL = 7; // 0x7
field public static final int TETHERING_WIFI = 0; // 0x0
field public static final int TETHERING_WIFI_P2P = 3; // 0x3
field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 7b769d4..2963f87 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -68,6 +68,8 @@
public static class Flags {
static final String TETHERING_REQUEST_WITH_SOFT_AP_CONFIG =
"com.android.net.flags.tethering_request_with_soft_ap_config";
+ static final String TETHERING_REQUEST_VIRTUAL =
+ "com.android.net.flags.tethering_request_virtual";
}
private static final String TAG = TetheringManager.class.getSimpleName();
@@ -195,10 +197,18 @@
public static final int TETHERING_WIGIG = 6;
/**
+ * VIRTUAL tethering type.
+ * @hide
+ */
+ @FlaggedApi(Flags.TETHERING_REQUEST_VIRTUAL)
+ @SystemApi
+ public static final int TETHERING_VIRTUAL = 7;
+
+ /**
* The int value of last tethering type.
* @hide
*/
- public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG;
+ public static final int MAX_TETHERING_TYPE = TETHERING_VIRTUAL;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
diff --git a/Tethering/res/values-fa/strings.xml b/Tethering/res/values-fa/strings.xml
index fdfd5c4..d7f2543 100644
--- a/Tethering/res/values-fa/strings.xml
+++ b/Tethering/res/values-fa/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="tethered_notification_title" msgid="5350162111436634622">"اشتراکگذاری اینترنت یا نقطه اتصال فعال است"</string>
- <string name="tethered_notification_message" msgid="2338023450330652098">"برای راهاندازی، تکضرب بزنید."</string>
+ <string name="tethered_notification_message" msgid="2338023450330652098">"برای راهاندازی، ضربه بزنید."</string>
<string name="disable_tether_notification_title" msgid="3183576627492925522">"اشتراکگذاری اینترنت غیرفعال است"</string>
<string name="disable_tether_notification_message" msgid="6655882039707534929">"برای جزئیات، با سرپرستتان تماس بگیرید"</string>
<string name="notification_channel_tethering_status" msgid="7030733422705019001">"وضعیت نقطه اتصال و اشتراکگذاری اینترنت"</string>
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 544ba01..fe5a0c6 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -34,7 +34,6 @@
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
import static com.android.networkstack.tethering.TetheringConfiguration.USE_SYNC_SM;
-import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface;
import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER;
@@ -75,19 +74,15 @@
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.SyncStateMachine.StateInfo;
import com.android.net.module.util.ip.InterfaceController;
-import com.android.net.module.util.ip.IpNeighborMonitor;
-import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
import com.android.networkstack.tethering.BpfCoordinator;
-import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
-import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.StateMachineShim;
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -137,8 +132,8 @@
private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString("00:00:00:00:00:00");
private static final String TAG = "IpServer";
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
private static final Class[] sMessageClasses = {
IpServer.class
};
@@ -189,12 +184,6 @@
return new DadProxy(handler, ifParams);
}
- /** Create an IpNeighborMonitor to be used by this IpServer */
- public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log,
- IpNeighborMonitor.NeighborEventConsumer consumer) {
- return new IpNeighborMonitor(handler, log, consumer);
- }
-
/** Create a RouterAdvertisementDaemon instance to be used by IpServer.*/
public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
return new RouterAdvertisementDaemon(ifParams);
@@ -234,13 +223,11 @@
public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IPSERVER + 9;
// new IPv6 tethering parameters need to be processed
public static final int CMD_IPV6_TETHER_UPDATE = BASE_IPSERVER + 10;
- // new neighbor cache entry on our interface
- public static final int CMD_NEIGHBOR_EVENT = BASE_IPSERVER + 11;
// request from DHCP server that it wants to have a new prefix
- public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 12;
+ public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 11;
// request from PrivateAddressCoordinator to restart tethering.
- public static final int CMD_NOTIFY_PREFIX_CONFLICT = BASE_IPSERVER + 13;
- public static final int CMD_SERVICE_FAILED_TO_START = BASE_IPSERVER + 14;
+ public static final int CMD_NOTIFY_PREFIX_CONFLICT = BASE_IPSERVER + 12;
+ public static final int CMD_SERVICE_FAILED_TO_START = BASE_IPSERVER + 13;
private final State mInitialState;
private final State mLocalHotspotState;
@@ -301,18 +288,9 @@
private List<TetheredClient> mDhcpLeases = Collections.emptyList();
private int mLastIPv6UpstreamIfindex = 0;
- private boolean mUpstreamSupportsBpf = false;
@NonNull
private Set<IpPrefix> mLastIPv6UpstreamPrefixes = Collections.emptySet();
- private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer {
- public void accept(NeighborEvent e) {
- sendMessage(CMD_NEIGHBOR_EVENT, e);
- }
- }
-
- private final IpNeighborMonitor mIpNeighborMonitor;
-
private LinkAddress mIpv4Address;
private final TetheringMetrics mTetheringMetrics;
@@ -346,15 +324,6 @@
mLastError = TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
- mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
- new MyNeighborEventConsumer());
-
- // IP neighbor monitor monitors the neighbor events for adding/removing IPv6 downstream rule
- // per client. If BPF offload is not supported, don't start listening for neighbor events.
- if (mBpfCoordinator.isUsingBpfOffload() && !mIpNeighborMonitor.start()) {
- mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
- }
-
mInitialState = new InitialState();
mLocalHotspotState = new LocalHotspotState();
mTetheredState = new TetheredState();
@@ -410,6 +379,22 @@
return mIpv4Address;
}
+ /** The IPv6 upstream interface index */
+ public int getIpv6UpstreamIfindex() {
+ return mLastIPv6UpstreamIfindex;
+ }
+
+ /** The IPv6 upstream interface prefixes */
+ @NonNull
+ public Set<IpPrefix> getIpv6UpstreamPrefixes() {
+ return Collections.unmodifiableSet(mLastIPv6UpstreamPrefixes);
+ }
+
+ /** The interface parameters which IpServer is using */
+ public InterfaceParams getInterfaceParams() {
+ return mInterfaceParams;
+ }
+
/**
* Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
* thread.
@@ -813,14 +798,15 @@
setRaParams(params);
// Not support BPF on virtual upstream interface
- final boolean upstreamSupportsBpf = upstreamIface != null && !isVcnInterface(upstreamIface);
final Set<IpPrefix> upstreamPrefixes = params != null ? params.prefixes : Set.of();
- updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamPrefixes,
- upstreamIfIndex, upstreamPrefixes, upstreamSupportsBpf);
+ // mBpfCoordinator#updateIpv6UpstreamInterface must be called before updating
+ // mLastIPv6UpstreamIfindex and mLastIPv6UpstreamPrefixes because BpfCoordinator will call
+ // IpServer#getIpv6UpstreamIfindex and IpServer#getIpv6UpstreamPrefixes to retrieve current
+ // upstream interface index and prefixes when handling upstream changes.
+ mBpfCoordinator.updateIpv6UpstreamInterface(this, upstreamIfIndex, upstreamPrefixes);
mLastIPv6LinkProperties = v6only;
mLastIPv6UpstreamIfindex = upstreamIfIndex;
mLastIPv6UpstreamPrefixes = upstreamPrefixes;
- mUpstreamSupportsBpf = upstreamSupportsBpf;
if (mDadProxy != null) {
mDadProxy.setUpstreamIface(upstreamIfaceParams);
}
@@ -964,77 +950,6 @@
}
}
- private int getInterfaceIndexForRule(int ifindex, boolean supportsBpf) {
- return supportsBpf ? ifindex : NO_UPSTREAM;
- }
-
- // Handles updates to IPv6 forwarding rules if the upstream or its prefixes change.
- private void updateIpv6ForwardingRules(int prevUpstreamIfindex,
- @NonNull Set<IpPrefix> prevUpstreamPrefixes, int upstreamIfindex,
- @NonNull Set<IpPrefix> upstreamPrefixes, boolean upstreamSupportsBpf) {
- // If the upstream interface has changed, remove all rules and re-add them with the new
- // upstream interface. If upstream is a virtual network, treated as no upstream.
- if (prevUpstreamIfindex != upstreamIfindex
- || !prevUpstreamPrefixes.equals(upstreamPrefixes)) {
- mBpfCoordinator.updateAllIpv6Rules(this, this.mInterfaceParams,
- getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf),
- upstreamPrefixes);
- }
- }
-
- // Handles updates to IPv6 downstream rules if a neighbor event is received.
- private void addOrRemoveIpv6Downstream(NeighborEvent e) {
- // mInterfaceParams must be non-null or the event would not have arrived.
- if (e == null) return;
- if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
- || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
- return;
- }
-
- // When deleting rules, we still need to pass a non-null MAC, even though it's ignored.
- // Do this here instead of in the Ipv6DownstreamRule constructor to ensure that we
- // never add rules with a null MAC, only delete them.
- MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
- Ipv6DownstreamRule rule = new Ipv6DownstreamRule(
- getInterfaceIndexForRule(mLastIPv6UpstreamIfindex, mUpstreamSupportsBpf),
- mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac);
- if (e.isValid()) {
- mBpfCoordinator.addIpv6DownstreamRule(this, rule);
- } else {
- mBpfCoordinator.removeIpv6DownstreamRule(this, rule);
- }
- }
-
- // TODO: consider moving into BpfCoordinator.
- private void updateClientInfoIpv4(NeighborEvent e) {
- if (e == null) return;
- if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress()
- || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
- return;
- }
-
- // When deleting clients, IpServer still need to pass a non-null MAC, even though it's
- // ignored. Do this here instead of in the ClientInfo constructor to ensure that
- // IpServer never add clients with a null MAC, only delete them.
- final MacAddress clientMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
- final ClientInfo clientInfo = new ClientInfo(mInterfaceParams.index,
- mInterfaceParams.macAddr, (Inet4Address) e.ip, clientMac);
- if (e.isValid()) {
- mBpfCoordinator.tetherOffloadClientAdd(this, clientInfo);
- } else {
- mBpfCoordinator.tetherOffloadClientRemove(this, clientInfo);
- }
- }
-
- private void handleNeighborEvent(NeighborEvent e) {
- if (mInterfaceParams != null
- && mInterfaceParams.index == e.ifindex
- && mInterfaceParams.hasMacAddress) {
- addOrRemoveIpv6Downstream(e);
- updateClientInfoIpv4(e);
- }
- }
-
private byte getHopLimit(String upstreamIface, int adjustTTL) {
try {
int upstreamHopLimit = Integer.parseUnsignedInt(
@@ -1069,7 +984,6 @@
switch (what) {
// Suppress some CMD_* to avoid log flooding.
case CMD_IPV6_TETHER_UPDATE:
- case CMD_NEIGHBOR_EVENT:
break;
default:
mLog.log(state.getName() + " got "
@@ -1141,14 +1055,6 @@
}
}
- private void startConntrackMonitoring() {
- mBpfCoordinator.startMonitoring(this);
- }
-
- private void stopConntrackMonitoring() {
- mBpfCoordinator.stopMonitoring(this);
- }
-
abstract class BaseServingState extends State {
private final int mDesiredInterfaceState;
@@ -1158,7 +1064,7 @@
@Override
public void enter() {
- startConntrackMonitoring();
+ mBpfCoordinator.addIpServer(IpServer.this);
startServingInterface();
@@ -1226,7 +1132,7 @@
}
stopIPv4();
- stopConntrackMonitoring();
+ mBpfCoordinator.removeIpServer(IpServer.this);
resetLinkProperties();
@@ -1397,8 +1303,8 @@
for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
mUpstreamIfaceSet = null;
- mBpfCoordinator.updateAllIpv6Rules(
- IpServer.this, IpServer.this.mInterfaceParams, NO_UPSTREAM, Set.of());
+ mBpfCoordinator.updateIpv6UpstreamInterface(IpServer.this, NO_UPSTREAM,
+ Collections.emptySet());
}
private void cleanupUpstreamInterface(String upstreamIface) {
@@ -1473,9 +1379,6 @@
}
}
break;
- case CMD_NEIGHBOR_EVENT:
- handleNeighborEvent((NeighborEvent) message.obj);
- break;
default:
return false;
}
@@ -1515,9 +1418,6 @@
class UnavailableState extends State {
@Override
public void enter() {
- // TODO: move mIpNeighborMonitor.stop() to TetheredState#exit, and trigger a neighbours
- // dump after starting mIpNeighborMonitor.
- mIpNeighborMonitor.stop();
mLastError = TETHER_ERROR_NO_ERROR;
sendInterfaceState(STATE_UNAVAILABLE);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 81e18ab..5c853f4 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;
@@ -77,6 +76,9 @@
import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.ip.ConntrackMonitor;
import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEventConsumer;
+import com.android.net.module.util.ip.IpNeighborMonitor;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer;
import com.android.net.module.util.netlink.ConntrackMessage;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkUtils;
@@ -182,6 +184,10 @@
private final BpfCoordinatorShim mBpfCoordinatorShim;
@NonNull
private final BpfConntrackEventConsumer mBpfConntrackEventConsumer;
+ @NonNull
+ private final IpNeighborMonitor mIpNeighborMonitor;
+ @NonNull
+ private final BpfNeighborEventConsumer mBpfNeighborEventConsumer;
// True if BPF offload is supported, false otherwise. The BPF offload could be disabled by
// a runtime resource overlay package or device configuration. This flag is only initialized
@@ -190,14 +196,6 @@
// to make it simpler. See also TetheringConfiguration.
private final boolean mIsBpfEnabled;
- // Tracks whether BPF tethering is started or not. This is set by tethering before it
- // starts the first IpServer and is cleared by tethering shortly before the last IpServer
- // is stopped. Note that rule updates (especially deletions, but sometimes additions as
- // well) may arrive when this is false. If they do, they must be communicated to netd.
- // Changes in data limits may also arrive when this is false, and if they do, they must
- // also be communicated to netd.
- private boolean mPollingStarted = false;
-
// Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert
// quota is interface independent and global for tether offload.
private long mRemainingAlertQuota = QUOTA_UNLIMITED;
@@ -280,9 +278,6 @@
private final HashMap<IpServer, HashMap<Inet4Address, ClientInfo>>
mTetherClients = new HashMap<>();
- // Set for which downstream is monitoring the conntrack netlink message.
- private final Set<IpServer> mMonitoringIpServers = new HashSet<>();
-
// Map of upstream interface IPv4 address to interface index.
// TODO: consider making the key to be unique because the upstream address is not unique. It
// is okay for now because there have only one upstream generally.
@@ -304,16 +299,19 @@
@Nullable
private UpstreamInfo mIpv4UpstreamInfo = null;
+ // The IpServers that are currently served by BpfCoordinator.
+ private final ArraySet<IpServer> mServedIpServers = new ArraySet<>();
+
// Runnable that used by scheduling next polling of stats.
private final Runnable mScheduledPollingStats = () -> {
updateForwardedStats();
- maybeSchedulePollingStats();
+ schedulePollingStats();
};
// Runnable that used by scheduling next refreshing of conntrack timeout.
private final Runnable mScheduledConntrackTimeoutUpdate = () -> {
refreshAllConntrackTimeouts();
- maybeScheduleConntrackTimeoutUpdate();
+ scheduleConntrackTimeoutUpdate();
};
// TODO: add BpfMap<TetherDownstream64Key, TetherDownstream64Value> retrieving function.
@@ -339,6 +337,11 @@
return new ConntrackMonitor(getHandler(), getSharedLog(), consumer);
}
+ /** Get ip neighbor monitor */
+ @NonNull public IpNeighborMonitor getIpNeighborMonitor(NeighborEventConsumer consumer) {
+ return new IpNeighborMonitor(getHandler(), getSharedLog(), consumer);
+ }
+
/** Get interface information for a given interface. */
@NonNull public InterfaceParams getInterfaceParams(String ifName) {
return InterfaceParams.getByName(ifName);
@@ -486,6 +489,9 @@
mBpfConntrackEventConsumer = new BpfConntrackEventConsumer();
mConntrackMonitor = mDeps.getConntrackMonitor(mBpfConntrackEventConsumer);
+ mBpfNeighborEventConsumer = new BpfNeighborEventConsumer();
+ mIpNeighborMonitor = mDeps.getIpNeighborMonitor(mBpfNeighborEventConsumer);
+
BpfTetherStatsProvider provider = new BpfTetherStatsProvider();
try {
mDeps.getNetworkStatsManager().registerNetworkStatsProvider(
@@ -505,37 +511,25 @@
}
/**
- * Start BPF tethering offload stats polling when the first upstream is started.
+ * Start BPF tethering offload stats and conntrack timeout polling.
* Note that this can be only called on handler thread.
- * TODO: Perhaps check BPF support before starting.
- * TODO: Start the stats polling only if there is any client on the downstream.
*/
- public void startPolling() {
- if (mPollingStarted) return;
+ private void startStatsAndConntrackTimeoutPolling() {
+ schedulePollingStats();
+ scheduleConntrackTimeoutUpdate();
- if (!isUsingBpf()) {
- mLog.i("BPF is not using");
- return;
- }
-
- mPollingStarted = true;
- maybeSchedulePollingStats();
- maybeScheduleConntrackTimeoutUpdate();
-
- mLog.i("Polling started");
+ mLog.i("Polling started.");
}
/**
- * Stop BPF tethering offload stats polling.
+ * Stop BPF tethering offload stats and conntrack timeout polling.
* The data limit cleanup and the tether stats maps cleanup are not implemented here.
* These cleanups rely on all IpServers calling #removeIpv6DownstreamRule. After the
* last rule is removed from the upstream, #removeIpv6DownstreamRule does the cleanup
* functionality.
* Note that this can be only called on handler thread.
*/
- public void stopPolling() {
- if (!mPollingStarted) return;
-
+ private void stopStatsAndConntrackTimeoutPolling() {
// Stop scheduled polling conntrack timeout.
if (mHandler.hasCallbacks(mScheduledConntrackTimeoutUpdate)) {
mHandler.removeCallbacks(mScheduledConntrackTimeoutUpdate);
@@ -545,9 +539,8 @@
mHandler.removeCallbacks(mScheduledPollingStats);
}
updateForwardedStats();
- mPollingStarted = false;
- mLog.i("Polling stopped");
+ mLog.i("Polling stopped.");
}
/**
@@ -568,7 +561,6 @@
/**
* Start conntrack message monitoring.
- * Note that this can be only called on handler thread.
*
* TODO: figure out a better logging for non-interesting conntrack message.
* For example, the following logging is an IPCTNL_MSG_CT_GET message but looks scary.
@@ -588,45 +580,23 @@
* +------------------+--------------------------------------------------------+
* See NetlinkMonitor#handlePacket, NetlinkMessage#parseNfMessage.
*/
- public void startMonitoring(@NonNull final IpServer ipServer) {
+ private void startConntrackMonitoring() {
// TODO: Wrap conntrackMonitor starting function into mBpfCoordinatorShim.
- if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
+ if (!mDeps.isAtLeastS()) return;
- if (mMonitoringIpServers.contains(ipServer)) {
- Log.wtf(TAG, "The same downstream " + ipServer.interfaceName()
- + " should not start monitoring twice.");
- return;
- }
-
- if (mMonitoringIpServers.isEmpty()) {
- mConntrackMonitor.start();
- mLog.i("Monitoring started");
- }
-
- mMonitoringIpServers.add(ipServer);
+ mConntrackMonitor.start();
+ mLog.i("Conntrack monitoring started.");
}
/**
* Stop conntrack event monitoring.
- * Note that this can be only called on handler thread.
*/
- public void stopMonitoring(@NonNull final IpServer ipServer) {
+ private void stopConntrackMonitoring() {
// TODO: Wrap conntrackMonitor stopping function into mBpfCoordinatorShim.
- if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
-
- // Ignore stopping monitoring if the monitor has never started for a given IpServer.
- if (!mMonitoringIpServers.contains(ipServer)) {
- mLog.e("Ignore stopping monitoring because monitoring has never started for "
- + ipServer.interfaceName());
- return;
- }
-
- mMonitoringIpServers.remove(ipServer);
-
- if (!mMonitoringIpServers.isEmpty()) return;
+ if (!mDeps.isAtLeastS()) return;
mConntrackMonitor.stop();
- mLog.i("Monitoring stopped");
+ mLog.i("Conntrack monitoring stopped.");
}
/**
@@ -689,9 +659,8 @@
/**
* Add IPv6 downstream rule.
- * Note that this can be only called on handler thread.
*/
- public void addIpv6DownstreamRule(
+ private void addIpv6DownstreamRule(
@NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
if (!isUsingBpf()) return;
@@ -707,9 +676,8 @@
/**
* Remove IPv6 downstream rule.
- * Note that this can be only called on handler thread.
*/
- public void removeIpv6DownstreamRule(
+ private void removeIpv6DownstreamRule(
@NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
if (!isUsingBpf()) return;
@@ -763,9 +731,8 @@
/**
* Delete all upstream and downstream rules for the passed-in IpServer, and if the new upstream
* is nonzero, reapply them to the new upstream.
- * Note that this can be only called on handler thread.
*/
- public void updateAllIpv6Rules(@NonNull final IpServer ipServer,
+ private void updateAllIpv6Rules(@NonNull final IpServer ipServer,
final InterfaceParams interfaceParams, int newUpstreamIfindex,
@NonNull final Set<IpPrefix> newUpstreamPrefixes) {
if (!isUsingBpf()) return;
@@ -887,6 +854,141 @@
}
/**
+ * Register an IpServer (downstream).
+ * Note that this can be only called on handler thread.
+ */
+ public void addIpServer(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+ if (mServedIpServers.contains(ipServer)) {
+ Log.wtf(TAG, "The same downstream " + ipServer.interfaceName()
+ + " should not add twice.");
+ return;
+ }
+
+ // Start monitoring and polling when the first IpServer is added.
+ if (mServedIpServers.isEmpty()) {
+ startStatsAndConntrackTimeoutPolling();
+ startConntrackMonitoring();
+ mIpNeighborMonitor.start();
+ mLog.i("Neighbor monitoring started.");
+ }
+ mServedIpServers.add(ipServer);
+ }
+
+ /**
+ * Unregister an IpServer (downstream).
+ * Note that this can be only called on handler thread.
+ */
+ public void removeIpServer(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+ if (!mServedIpServers.contains(ipServer)) {
+ mLog.e("Ignore removing because IpServer has never started for "
+ + ipServer.interfaceName());
+ return;
+ }
+ mServedIpServers.remove(ipServer);
+
+ // Stop monitoring and polling when the last IpServer is removed.
+ if (mServedIpServers.isEmpty()) {
+ stopStatsAndConntrackTimeoutPolling();
+ stopConntrackMonitoring();
+ mIpNeighborMonitor.stop();
+ mLog.i("Neighbor monitoring stopped.");
+ }
+ }
+
+ /**
+ * Update upstream interface and its prefixes.
+ * Note that this can be only called on handler thread.
+ */
+ public void updateIpv6UpstreamInterface(@NonNull final IpServer ipServer, int upstreamIfindex,
+ @NonNull Set<IpPrefix> upstreamPrefixes) {
+ if (!isUsingBpf()) return;
+
+ // If the upstream interface has changed, remove all rules and re-add them with the new
+ // upstream interface. If upstream is a virtual network, treated as no upstream.
+ final int prevUpstreamIfindex = ipServer.getIpv6UpstreamIfindex();
+ final InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ final Set<IpPrefix> prevUpstreamPrefixes = ipServer.getIpv6UpstreamPrefixes();
+ if (prevUpstreamIfindex != upstreamIfindex
+ || !prevUpstreamPrefixes.equals(upstreamPrefixes)) {
+ final boolean upstreamSupportsBpf = checkUpstreamSupportsBpf(upstreamIfindex);
+ updateAllIpv6Rules(ipServer, interfaceParams,
+ getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf),
+ upstreamPrefixes);
+ }
+ }
+
+ private boolean checkUpstreamSupportsBpf(int upstreamIfindex) {
+ final String iface = mInterfaceNames.get(upstreamIfindex);
+ return iface != null && !isVcnInterface(iface);
+ }
+
+ private int getInterfaceIndexForRule(int ifindex, boolean supportsBpf) {
+ return supportsBpf ? ifindex : NO_UPSTREAM;
+ }
+
+ // Handles updates to IPv6 downstream rules if a neighbor event is received.
+ private void addOrRemoveIpv6Downstream(@NonNull IpServer ipServer, NeighborEvent e) {
+ // mInterfaceParams must be non-null or the event would not have arrived.
+ if (e == null) return;
+ if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ // When deleting rules, we still need to pass a non-null MAC, even though it's ignored.
+ // Do this here instead of in the Ipv6DownstreamRule constructor to ensure that we
+ // never add rules with a null MAC, only delete them.
+ final InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ if (interfaceParams == null || interfaceParams.macAddr == null) return;
+ final int lastIpv6UpstreamIfindex = ipServer.getIpv6UpstreamIfindex();
+ final boolean isUpstreamSupportsBpf = checkUpstreamSupportsBpf(lastIpv6UpstreamIfindex);
+ MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
+ Ipv6DownstreamRule rule = new Ipv6DownstreamRule(
+ getInterfaceIndexForRule(lastIpv6UpstreamIfindex, isUpstreamSupportsBpf),
+ interfaceParams.index, (Inet6Address) e.ip, interfaceParams.macAddr, dstMac);
+ if (e.isValid()) {
+ addIpv6DownstreamRule(ipServer, rule);
+ } else {
+ removeIpv6DownstreamRule(ipServer, rule);
+ }
+ }
+
+ private void updateClientInfoIpv4(@NonNull IpServer ipServer, NeighborEvent e) {
+ if (e == null) return;
+ if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ if (interfaceParams == null) return;
+
+ // When deleting clients, IpServer still need to pass a non-null MAC, even though it's
+ // ignored. Do this here instead of in the ClientInfo constructor to ensure that
+ // IpServer never add clients with a null MAC, only delete them.
+ final MacAddress clientMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
+ final ClientInfo clientInfo = new ClientInfo(interfaceParams.index,
+ interfaceParams.macAddr, (Inet4Address) e.ip, clientMac);
+ if (e.isValid()) {
+ tetherOffloadClientAdd(ipServer, clientInfo);
+ } else {
+ tetherOffloadClientRemove(ipServer, clientInfo);
+ }
+ }
+
+ private void handleNeighborEvent(@NonNull IpServer ipServer, NeighborEvent e) {
+ InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ if (interfaceParams != null
+ && interfaceParams.index == e.ifindex
+ && interfaceParams.hasMacAddress) {
+ addOrRemoveIpv6Downstream(ipServer, e);
+ updateClientInfoIpv4(ipServer, e);
+ }
+ }
+
+ /**
* Clear all forwarding IPv4 rules for a given client.
* Note that this can be only called on handler thread.
*/
@@ -1137,7 +1239,7 @@
// Note that EthernetTetheringTest#isTetherConfigBpfOffloadEnabled relies on
// "mIsBpfEnabled" to check tethering config via dumpsys. Beware of the change if any.
pw.println("mIsBpfEnabled: " + mIsBpfEnabled);
- pw.println("Polling " + (mPollingStarted ? "started" : "not started"));
+ pw.println("Polling " + (mServedIpServers.isEmpty() ? "not started" : "started"));
pw.println("Stats provider " + (mStatsProvider != null
? "registered" : "not registered"));
pw.println("Upstream quota: " + mInterfaceQuotas.toString());
@@ -1343,19 +1445,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 +1464,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;
@@ -2052,6 +2141,15 @@
}
}
+ @VisibleForTesting
+ private class BpfNeighborEventConsumer implements NeighborEventConsumer {
+ public void accept(NeighborEvent e) {
+ for (IpServer ipServer : mServedIpServers) {
+ handleNeighborEvent(ipServer, e);
+ }
+ }
+ }
+
private boolean isBpfEnabled() {
final TetheringConfiguration config = mDeps.getTetherConfig();
return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */;
@@ -2379,9 +2477,7 @@
});
}
- private void maybeSchedulePollingStats() {
- if (!mPollingStarted) return;
-
+ private void schedulePollingStats() {
if (mHandler.hasCallbacks(mScheduledPollingStats)) {
mHandler.removeCallbacks(mScheduledPollingStats);
}
@@ -2389,9 +2485,7 @@
mHandler.postDelayed(mScheduledPollingStats, getPollingInterval());
}
- private void maybeScheduleConntrackTimeoutUpdate() {
- if (!mPollingStarted) return;
-
+ private void scheduleConntrackTimeoutUpdate() {
if (mHandler.hasCallbacks(mScheduledConntrackTimeoutUpdate)) {
mHandler.removeCallbacks(mScheduledConntrackTimeoutUpdate);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 873961a..0ff89d3 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -38,6 +38,7 @@
import static android.net.TetheringManager.TETHERING_INVALID;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHERING_WIGIG;
@@ -173,8 +174,8 @@
public class Tethering {
private static final String TAG = Tethering.class.getSimpleName();
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
private static final Class[] sMessageClasses = {
Tethering.class, TetherMainSM.class, IpServer.class
@@ -241,9 +242,6 @@
private final TetherMainSM mTetherMainSM;
private final OffloadController mOffloadController;
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
- // TODO: Figure out how to merge this and other downstream-tracking objects
- // into a single coherent structure.
- private final HashSet<IpServer> mForwardedDownstreams;
private final VersionedBroadcastListener mCarrierConfigChange;
private final TetheringDependencies mDeps;
private final EntitlementManager mEntitlementMgr;
@@ -271,8 +269,6 @@
private boolean mRndisEnabled; // track the RNDIS function enabled state
private boolean mNcmEnabled; // track the NCM function enabled state
- // True iff. WiFi tethering should be started when soft AP is ready.
- private boolean mWifiTetherRequested;
private Network mTetherUpstream;
private TetherStatesParcel mTetherStatesParcel;
private boolean mDataSaverEnabled = false;
@@ -283,6 +279,7 @@
private TetheredInterfaceRequestShim mBluetoothIfaceRequest;
private String mConfiguredEthernetIface;
private String mConfiguredBluetoothIface;
+ private String mConfiguredVirtualIface;
private EthernetCallback mEthernetCallback;
private TetheredInterfaceCallbackShim mBluetoothCallback;
private SettingsObserver mSettingsObserver;
@@ -329,7 +326,6 @@
(what, obj) -> {
mTetherMainSM.sendMessage(TetherMainSM.EVENT_UPSTREAM_CALLBACK, what, 0, obj);
});
- mForwardedDownstreams = new HashSet<>();
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
@@ -725,6 +721,9 @@
case TETHERING_ETHERNET:
result = setEthernetTethering(enable);
break;
+ case TETHERING_VIRTUAL:
+ result = setVirtualMachineTethering(enable);
+ break;
default:
Log.w(TAG, "Invalid tether type.");
result = TETHER_ERROR_UNKNOWN_TYPE;
@@ -763,7 +762,6 @@
}
if ((enable && mgr.startTetheredHotspot(null /* use existing softap config */))
|| (!enable && mgr.stopSoftAp())) {
- mWifiTetherRequested = enable;
return TETHER_ERROR_NO_ERROR;
}
} finally {
@@ -979,6 +977,21 @@
}
}
+ private int setVirtualMachineTethering(final boolean enable) {
+ // TODO(340377643): Use bridge ifname when it's introduced, not fixed TAP ifname.
+ if (enable) {
+ mConfiguredVirtualIface = "avf_tap_fixed";
+ enableIpServing(
+ TETHERING_VIRTUAL,
+ mConfiguredVirtualIface,
+ getRequestedState(TETHERING_VIRTUAL));
+ } else if (mConfiguredVirtualIface != null) {
+ ensureIpServerStopped(mConfiguredVirtualIface);
+ mConfiguredVirtualIface = null;
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
void tether(String iface, int requestedState, final IIntResultListener listener) {
mHandler.post(() -> {
try {
@@ -1470,10 +1483,6 @@
}
private void disableWifiIpServing(String ifname, int apState) {
- // Regardless of whether we requested this transition, the AP has gone
- // down. Don't try to tether again unless we're requested to do so.
- mWifiTetherRequested = false;
-
mLog.log("Canceling WiFi tethering request - interface=" + ifname + " state=" + apState);
disableWifiIpServingCommon(TETHERING_WIFI, ifname);
@@ -1505,8 +1514,7 @@
private void enableWifiIpServing(String ifname, int wifiIpMode) {
mLog.log("request WiFi tethering - interface=" + ifname + " state=" + wifiIpMode);
- // Map wifiIpMode values to IpServer.Callback serving states, inferring
- // from mWifiTetherRequested as a final "best guess".
+ // Map wifiIpMode values to IpServer.Callback serving states.
final int ipServingMode;
switch (wifiIpMode) {
case IFACE_IP_MODE_TETHERED:
@@ -1653,11 +1661,6 @@
mLog.log(state.getName() + " got " + sMagicDecoderRing.get(what, Integer.toString(what)));
}
- private boolean upstreamWanted() {
- if (!mForwardedDownstreams.isEmpty()) return true;
- return mWifiTetherRequested;
- }
-
// Needed because the canonical source of upstream truth is just the
// upstream interface set, |mCurrentUpstreamIfaceSet|.
private boolean pertainsToCurrentUpstream(UpstreamNetworkState ns) {
@@ -1715,12 +1718,16 @@
private final ArrayList<IpServer> mNotifyList;
private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
private final OffloadWrapper mOffload;
+ // TODO: Figure out how to merge this and other downstream-tracking objects
+ // into a single coherent structure.
+ private final HashSet<IpServer> mForwardedDownstreams;
private static final int UPSTREAM_SETTLE_TIME_MS = 10000;
TetherMainSM(String name, Looper looper, TetheringDependencies deps) {
super(name, looper);
+ mForwardedDownstreams = new HashSet<>();
mInitialState = new InitialState();
mTetherModeAliveState = new TetherModeAliveState();
mSetIpForwardingEnabledErrorState = new SetIpForwardingEnabledErrorState();
@@ -2056,6 +2063,10 @@
}
}
+ private boolean upstreamWanted() {
+ return !mForwardedDownstreams.isEmpty();
+ }
+
class TetherModeAliveState extends State {
boolean mUpstreamWanted = false;
boolean mTryCell = true;
@@ -2078,9 +2089,6 @@
chooseUpstreamType(true);
mTryCell = false;
}
-
- // TODO: Check the upstream interface if it is managed by BPF offload.
- mBpfCoordinator.startPolling();
}
@Override
@@ -2094,7 +2102,6 @@
reportUpstreamChanged(null);
mNotificationUpdater.onUpstreamCapabilitiesChanged(null);
}
- mBpfCoordinator.stopPolling();
mTetheringMetrics.cleanup();
}
@@ -2393,6 +2400,9 @@
hasCallingPermission(NETWORK_SETTINGS)
|| hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
|| hasCallingPermission(NETWORK_STACK);
+ if (callback == null) {
+ throw new NullPointerException();
+ }
mHandler.post(() -> {
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
@@ -2651,7 +2661,7 @@
}
pw.println(" - lastError = " + tetherState.lastError);
}
- pw.println("Upstream wanted: " + upstreamWanted());
+ pw.println("Upstream wanted: " + mTetherMainSM.upstreamWanted());
pw.println("Current upstream interface(s): " + mCurrentUpstreamIfaceSet);
pw.decreaseIndent();
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 9dfd225..3f86056 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -156,6 +156,7 @@
/**
* Get a reference to BluetoothAdapter to be used by tethering.
*/
+ @Nullable
public abstract BluetoothAdapter getBluetoothAdapter();
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index aa73819..623f502 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -30,6 +30,7 @@
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.net.IIntResultListener;
@@ -377,7 +378,11 @@
@Override
public BluetoothAdapter getBluetoothAdapter() {
- return BluetoothAdapter.getDefaultAdapter();
+ final BluetoothManager btManager = getSystemService(BluetoothManager.class);
+ if (btManager == null) {
+ return null;
+ }
+ return btManager.getAdapter();
}
};
}
diff --git a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
index 078a35f..c236188 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
@@ -22,7 +22,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
+import com.android.net.module.util.SyncStateMachine;
+import com.android.net.module.util.SyncStateMachine.StateInfo;
import java.util.List;
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 07fa733..337d408 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -33,6 +33,7 @@
"net-tests-utils",
"net-utils-device-common",
"net-utils-device-common-bpf",
+ "net-utils-device-common-struct-base",
"testables",
"connectivity-net-module-utils-bpf",
],
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 2933a44..3944a8a 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -16,7 +16,6 @@
package android.net;
-import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.TETHER_PRIVILEGED;
@@ -42,7 +41,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -65,7 +63,6 @@
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.testutils.HandlerUtils;
@@ -74,7 +71,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.BeforeClass;
import java.io.FileDescriptor;
import java.net.Inet4Address;
@@ -83,8 +79,10 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -104,7 +102,7 @@
// Used to check if any tethering interface is available. Choose 200ms to be request timeout
// because the average interface requested time on cuttlefish@acloud is around 10ms.
// See TetheredInterfaceRequester.getInterface, isInterfaceForTetheringAvailable.
- private static final int AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS = 200;
+ private static final int SHORT_TIMEOUT_MS = 1000;
private static final int TETHER_REACHABILITY_ATTEMPTS = 20;
protected static final long WAIT_RA_TIMEOUT_MS = 2000;
@@ -142,16 +140,18 @@
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);
+ private static final List<String> sCallbackErrors =
+ Collections.synchronizedList(new ArrayList<>());
// Late initialization in setUp()
private boolean mRunTests;
private HandlerThread mHandlerThread;
private Handler mHandler;
- private TetheredInterfaceRequester mTetheredInterfaceRequester;
+ protected TetheredInterfaceRequester mTetheredInterfaceRequester;
// Late initialization in initTetheringTester().
private TapPacketReader mUpstreamReader;
@@ -164,33 +164,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;
- 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());
@@ -201,6 +174,7 @@
assumeTrue(mRunTests);
mTetheredInterfaceRequester = new TetheredInterfaceRequester();
+ sCallbackErrors.clear();
}
private boolean isEthernetTetheringSupported() throws Exception {
@@ -268,47 +242,37 @@
maybeUnregisterTetheringEventCallback(mTetheringEventCallback);
mTetheringEventCallback = null;
- runAsShell(NETWORK_SETTINGS, () -> mTetheredInterfaceRequester.release());
setIncludeTestInterfaces(false);
}
@After
public void tearDown() throws Exception {
+ if (mTetheredInterfaceRequester != null) {
+ mTetheredInterfaceRequester.release();
+ }
try {
if (mRunTests) cleanUp();
} finally {
mHandlerThread.quitSafely();
mHandlerThread.join();
}
+
+ if (sCallbackErrors.size() > 0) {
+ fail("Some callbacks had errors: " + sCallbackErrors);
+ }
}
- protected static boolean isInterfaceForTetheringAvailable() throws Exception {
- // Before T, all ethernet interfaces could be used for server mode. Instead of
- // waiting timeout, just checking whether the system currently has any
- // ethernet interface is more reliable.
- if (!SdkLevel.isAtLeastT()) {
- return runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS, () -> sEm.isAvailable());
- }
-
+ protected boolean isInterfaceForTetheringAvailable() throws Exception {
// If previous test case doesn't release tethering interface successfully, the other tests
// after that test may be skipped as unexcepted.
// TODO: figure out a better way to check default tethering interface existenion.
- final TetheredInterfaceRequester requester = new TetheredInterfaceRequester();
- try {
- // Use short timeout (200ms) for requesting an existing interface, if any, because
- // it should reurn faster than requesting a new tethering interface. Using default
- // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
- // test module timeout on internal testing.
- // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
- // this check into #setUpOnce.
- return requester.getInterface(AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS) != null;
- } catch (TimeoutException e) {
- return false;
- } finally {
- runAsShell(NETWORK_SETTINGS, () -> {
- requester.release();
- });
- }
+ // Use short timeout (200ms) for requesting an existing interface, if any, because
+ // it should reurn faster than requesting a new tethering interface. Using default
+ // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
+ // test module timeout on internal testing.
+ // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
+ // this check into #setUpOnce.
+ return mTetheredInterfaceRequester.isPhysicalInterfaceAvailable(SHORT_TIMEOUT_MS);
}
protected static void setIncludeTestInterfaces(boolean include) {
@@ -323,14 +287,6 @@
});
}
- protected String getTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.getInterface();
- }
-
- protected CompletableFuture<String> requestTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.requestInterface();
- }
-
protected static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
long timeoutMs) {
final long deadline = SystemClock.uptimeMillis() + timeoutMs;
@@ -391,7 +347,7 @@
}
@Override
public void onTetheredInterfacesChanged(List<String> interfaces) {
- fail("Should only call callback that takes a Set<TetheringInterface>");
+ addCallbackError("Should only call callback that takes a Set<TetheringInterface>");
}
@Override
@@ -412,7 +368,7 @@
@Override
public void onLocalOnlyInterfacesChanged(List<String> interfaces) {
- fail("Should only call callback that takes a Set<TetheringInterface>");
+ addCallbackError("Should only call callback that takes a Set<TetheringInterface>");
}
@Override
@@ -481,7 +437,7 @@
// Ignore stale callbacks registered by previous test cases.
if (mUnregistered) return;
- fail("TetheringEventCallback got error:" + error + " on iface " + ifName);
+ addCallbackError("TetheringEventCallback got error:" + error + " on iface " + ifName);
}
@Override
@@ -536,6 +492,11 @@
}
}
+ private static void addCallbackError(String error) {
+ Log.e(TAG, error);
+ sCallbackErrors.add(error);
+ }
+
protected static MyTetheringEventCallback enableEthernetTethering(String iface,
TetheringRequest request, Network expectedUpstream) throws Exception {
// Enable ethernet tethering with null expectedUpstream means the test accept any upstream
@@ -562,7 +523,7 @@
@Override
public void onTetheringFailed(int resultCode) {
- fail("Unexpectedly got onTetheringFailed");
+ addCallbackError("Unexpectedly got onTetheringFailed");
}
};
Log.d(TAG, "Starting Ethernet tethering");
@@ -619,6 +580,11 @@
private TetheredInterfaceRequest mRequest;
private final CompletableFuture<String> mFuture = new CompletableFuture<>();
+ TetheredInterfaceRequester() {
+ mRequest = runAsShell(NETWORK_SETTINGS, () ->
+ sEm.requestTetheredInterface(c -> c.run() /* executor */, this));
+ }
+
@Override
public void onAvailable(String iface) {
Log.d(TAG, "Ethernet interface available: " + iface);
@@ -630,28 +596,21 @@
mFuture.completeExceptionally(new IllegalStateException("onUnavailable received"));
}
- public CompletableFuture<String> requestInterface() {
- assertNull("BUG: more than one tethered interface request", mRequest);
- Log.d(TAG, "Requesting tethered interface");
- mRequest = runAsShell(NETWORK_SETTINGS, () ->
- sEm.requestTetheredInterface(c -> c.run() /* executor */, this));
- return mFuture;
- }
-
- public String getInterface(int timeout) throws Exception {
- return requestInterface().get(timeout, TimeUnit.MILLISECONDS);
+ public boolean isPhysicalInterfaceAvailable(int timeout) {
+ try {
+ final String iface = mFuture.get(timeout, TimeUnit.MILLISECONDS);
+ return !iface.startsWith("testtap");
+ } catch (Exception e) {
+ return false;
+ }
}
public String getInterface() throws Exception {
- return getInterface(TIMEOUT_MS);
+ return mFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public void release() {
- if (mRequest != null) {
- mFuture.obtrudeException(new IllegalStateException("Request already released"));
- mRequest.release();
- mRequest = null;
- }
+ runAsShell(NETWORK_SETTINGS, () -> mRequest.release());
}
}
@@ -672,7 +631,10 @@
lp.setLinkAddresses(addresses);
lp.setDnsServers(dnses);
- return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(sContext, lp, TIMEOUT_MS));
+ // TODO: initTestNetwork can take up to 15 seconds on a workstation. Investigate when and
+ // why this is the case. It is unclear whether a 30 second timeout is enough when running
+ // these tests in the much slower test infra.
+ return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(sContext, lp, 30_000));
}
protected void sendDownloadPacketUdp(@NonNull final InetAddress srcIp,
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 4949eaa..5c258b2 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.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -74,8 +75,6 @@
import java.util.Collection;
import java.util.List;
import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@RunWith(AndroidJUnit4.class)
@@ -150,6 +149,16 @@
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
};
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ // TODO: See b/318121782#comment4. Register an ethernet InterfaceStateListener, and wait for
+ // the callback to report client mode. This happens as soon as both
+ // TetheredInterfaceRequester and the tethering code itself have released the interface,
+ // i.e. after stopTethering() has completed.
+ Thread.sleep(3000);
+ }
+
@Test
public void testVirtualEthernetAlreadyExists() throws Exception {
// This test requires manipulating packets. Skip if there is a physical Ethernet connected.
@@ -171,7 +180,7 @@
Log.d(TAG, "Including test interfaces");
setIncludeTestInterfaces(true);
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -193,8 +202,6 @@
// This test requires manipulating packets. Skip if there is a physical Ethernet connected.
assumeFalse(isInterfaceForTetheringAvailable());
- CompletableFuture<String> futureIface = requestTetheredInterface();
-
setIncludeTestInterfaces(true);
TestNetworkInterface downstreamIface = null;
@@ -204,7 +211,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -234,7 +241,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -308,7 +315,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -358,7 +365,7 @@
MyTetheringEventCallback tetheringEventCallback = null;
try {
// Get an interface to use.
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
// Enable Ethernet tethering and check that it starts.
tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
@@ -479,17 +486,23 @@
// TODO: test BPF offload maps {rule, stats}.
}
- // Test network topology:
- //
- // public network (rawip) private network
- // | UE |
- // +------------+ V +------------+------------+ V +------------+
- // | Sever +---------+ Upstream | Downstream +---------+ Client |
- // +------------+ +------------+------------+ +------------+
- // remote ip public ip private ip
- // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
- //
- private void runUdp4Test() throws Exception {
+
+ /**
+ * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
+ * using which data path.
+ */
+ @Test
+ public void testTetherUdpV4() throws Exception {
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
+ //
final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
toList(TEST_IP4_DNS));
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
@@ -511,15 +524,6 @@
sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
}
- /**
- * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
- * using which data path.
- */
- @Test
- public void testTetherUdpV4() throws Exception {
- runUdp4Test();
- }
-
// Test network topology:
//
// public network (rawip) private network
@@ -569,7 +573,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
@@ -677,7 +681,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
// [1] Send DNS query.
@@ -721,7 +725,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index a80e49e..c4d5636 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -45,6 +45,7 @@
"junit-params",
"connectivity-net-module-utils-bpf",
"net-utils-device-common-bpf",
+ "net-utils-device-common-struct-base",
],
jni_libs: [
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..47aebe8 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;
@@ -56,11 +69,26 @@
private static final int TEST_MAP_SIZE = 16;
private static final String TETHER_DOWNSTREAM6_FS_PATH =
"/sys/fs/bpf/tethering/map_test_tether_downstream6_map";
+ private static final String TETHER2_DOWNSTREAM6_FS_PATH =
+ "/sys/fs/bpf/tethering/map_test_tether2_downstream6_map";
+ private static final String TETHER3_DOWNSTREAM6_FS_PATH =
+ "/sys/fs/bpf/tethering/map_test_tether3_downstream6_map";
private ArrayMap<TetherDownstream6Key, Tether6Value> mTestData;
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 +110,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
+ ? SingleWriterBpfMap.getSingleton(TETHER2_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));
@@ -125,7 +158,7 @@
assertEquals(OsConstants.EPERM, expected.errno);
}
}
- try (BpfMap writeOnlyMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_WRONLY,
+ try (BpfMap writeOnlyMap = new BpfMap<>(TETHER3_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_WRONLY,
TetherDownstream6Key.class, Tether6Value.class)) {
assertNotNull(writeOnlyMap);
try {
@@ -357,6 +390,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 +448,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 +468,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 +476,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);
+ // 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 = SingleWriterBpfMap.getSingleton(TETHER2_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+ final BpfMap uncachedMap = new BpfMap(TETHER_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+
+ // Ensure the maps are not empty.
+ for (int i = 0; i < mTestData.size(); i++) {
+ cachedMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ uncachedMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ }
+
+ 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/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index a7064e8..00eb3b1 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -53,7 +53,6 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -94,7 +93,6 @@
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.ip.IpNeighborMonitor;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
@@ -174,7 +172,6 @@
@Mock private IDhcpServer mDhcpServer;
@Mock private DadProxy mDadProxy;
@Mock private RouterAdvertisementDaemon mRaDaemon;
- @Mock private IpNeighborMonitor mIpNeighborMonitor;
@Mock private IpServer.Dependencies mDependencies;
@Mock private PrivateAddressCoordinator mAddressCoordinator;
private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinatorManager =
@@ -213,20 +210,17 @@
mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
}
- doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(), any());
-
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp);
when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
when(mBpfCoordinator.isUsingBpfOffload()).thenReturn(usingBpfOffload);
mIpServer = createIpServer(interfaceType);
- verify(mIpNeighborMonitor).start();
mIpServer.start();
// Starting the state machine always puts us in a consistent state and notifies
// the rest of the world that we've changed from an unknown to available state.
mLooper.dispatchAll();
- reset(mNetd, mCallback, mIpNeighborMonitor);
+ reset(mNetd, mCallback);
when(mRaDaemon.start()).thenReturn(true);
}
@@ -242,6 +236,7 @@
throws Exception {
initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
+ verify(mBpfCoordinator).addIpServer(mIpServer);
if (upstreamIface != null) {
InterfaceParams interfaceParams = mDependencies.getInterfaceParams(upstreamIface);
assertNotNull("missing upstream interface: " + upstreamIface, interfaceParams);
@@ -250,8 +245,12 @@
lp.setLinkAddresses(upstreamAddresses);
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
Set<IpPrefix> upstreamPrefixes = getTetherableIpv6Prefixes(lp.getLinkAddresses());
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, interfaceParams.index, upstreamPrefixes);
+ // One is called when handling CMD_TETHER_CONNECTION_CHANGED and the other one is called
+ // when upstream's LinkProperties is updated (updateUpstreamIPv6LinkProperties)
+ verify(mBpfCoordinator, times(2)).maybeAddUpstreamToLookupTable(
+ interfaceParams.index, upstreamIface);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, interfaceParams.index, upstreamPrefixes);
}
reset(mNetd, mBpfCoordinator, mCallback, mAddressCoordinator);
when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
@@ -314,8 +313,6 @@
@Test
public void startsOutAvailable() throws Exception {
- when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
- .thenReturn(mIpNeighborMonitor);
mIpServer = createIpServer(TETHERING_BLUETOOTH);
mIpServer.start();
mLooper.dispatchAll();
@@ -557,8 +554,8 @@
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ inOrder.verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
// When tethering stops, upstream interface is set to zero and thus clearing all upstream
// rules. Downstream rules are needed to be cleared explicitly by calling
// BpfCoordinator#clearAllIpv6Rules in TetheredState#exit.
@@ -570,7 +567,7 @@
argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
- inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
+ inOrder.verify(mBpfCoordinator).removeIpServer(mIpServer);
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
@@ -765,8 +762,8 @@
lp.setInterfaceName(UPSTREAM_IFACE2);
lp.setLinkAddresses(UPSTREAM_ADDRESSES);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES);
reset(mBpfCoordinator);
// Upstream link addresses change result in updating the rules.
@@ -774,8 +771,8 @@
lp2.setInterfaceName(UPSTREAM_IFACE2);
lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
reset(mBpfCoordinator);
// When the upstream is lost, rules are removed.
@@ -784,53 +781,54 @@
// - processMessage CMD_TETHER_CONNECTION_CHANGED for the upstream is lost.
// - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost.
// See dispatchTetherConnectionChanged.
- verify(mBpfCoordinator, times(2)).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator, times(2)).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
// If the upstream is IPv4-only, no rules are added.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- verify(mBpfCoordinator, never()).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator, never()).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
// Rules are added again once upstream IPv6 connectivity is available.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
reset(mBpfCoordinator);
// If upstream IPv6 connectivity is lost, rules are removed.
dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
// When upstream IPv6 connectivity comes back, rules are added.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
reset(mBpfCoordinator);
// When the downstream interface goes down, rules are removed.
mIpServer.stop();
mLooper.dispatchAll();
verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator).removeIpServer(mIpServer);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
}
@Test
- public void stopNeighborMonitoringWhenInterfaceDown() throws Exception {
+ public void removeIpServerWhenInterfaceDown() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES,
false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
mIpServer.stop();
mLooper.dispatchAll();
- verify(mIpNeighborMonitor).stop();
+ verify(mBpfCoordinator).removeIpServer(mIpServer);
}
private LinkProperties buildIpv6OnlyLinkProperties(final String iface) {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 47ecf58..e54a7e0 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -25,8 +25,6 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStats.UID_TETHERING;
-import static android.net.TetheringManager.TETHERING_WIFI;
-import static android.net.ip.IpServer.STATE_TETHERED;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
@@ -79,9 +77,7 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -100,11 +96,9 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkStats;
-import android.net.RoutingCoordinatorManager;
import android.net.TetherOffloadRuleParcel;
import android.net.TetherStatsParcel;
import android.net.ip.IpServer;
-import android.net.ip.RouterAdvertisementDaemon;
import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
@@ -119,12 +113,10 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
-import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.bpf.Tether4Key;
@@ -143,8 +135,6 @@
import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule;
-import com.android.networkstack.tethering.metrics.TetheringMetrics;
-import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -209,11 +199,6 @@
private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b");
private static final MacAddress MAC_NULL = MacAddress.fromString("00:00:00:00:00:00");
- private static final LinkAddress UPSTREAM_ADDRESS = new LinkAddress("2001:db8:0:1234::168/64");
- private static final LinkAddress UPSTREAM_ADDRESS2 = new LinkAddress("2001:db8:0:abcd::168/64");
- private static final Set<LinkAddress> UPSTREAM_ADDRESSES = Set.of(UPSTREAM_ADDRESS);
- private static final Set<LinkAddress> UPSTREAM_ADDRESSES2 =
- Set.of(UPSTREAM_ADDRESS, UPSTREAM_ADDRESS2);
private static final IpPrefix UPSTREAM_PREFIX = new IpPrefix("2001:db8:0:1234::/64");
private static final IpPrefix UPSTREAM_PREFIX2 = new IpPrefix("2001:db8:0:abcd::/64");
private static final Set<IpPrefix> UPSTREAM_PREFIXES = Set.of(UPSTREAM_PREFIX);
@@ -449,13 +434,6 @@
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
@Mock private IpNeighborMonitor mIpNeighborMonitor;
- @Mock private RouterAdvertisementDaemon mRaDaemon;
- @Mock private IpServer.Dependencies mIpServerDeps;
- @Mock private IpServer.Callback mIpServerCallback;
- @Mock private PrivateAddressCoordinator mAddressCoordinator;
- private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinatorManager =
- new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null);
- @Mock private TetheringMetrics mTetheringMetrics;
// Late init since methods must be called by the thread that created this object.
private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
@@ -573,24 +551,8 @@
@Before public void setUp() {
MockitoAnnotations.initMocks(this);
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */);
-
- // Simulate the behavior of RoutingCoordinator
- if (null != mRoutingCoordinatorManager.value) {
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any());
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- mNetd.tetherRemoveForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any());
- }
+ when(mIpServer.getInterfaceParams()).thenReturn(DOWNSTREAM_IFACE_PARAMS);
+ when(mIpServer2.getInterfaceParams()).thenReturn(DOWNSTREAM_IFACE_PARAMS2);
}
private void waitForIdle() {
@@ -603,70 +565,39 @@
when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]);
}
- @NonNull
- private IpServer makeAndStartIpServer(String interfaceName, BpfCoordinator bpfCoordinator)
- throws Exception {
- final LinkAddress testAddress = new LinkAddress("192.168.42.5/24");
- when(mIpServerDeps.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
- when(mIpServerDeps.getInterfaceParams(DOWNSTREAM_IFACE)).thenReturn(
- DOWNSTREAM_IFACE_PARAMS);
- when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS);
- when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2);
- when(mIpServerDeps.getInterfaceParams(IPSEC_IFACE)).thenReturn(IPSEC_IFACE_PARAMS);
- when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
- anyBoolean())).thenReturn(testAddress);
- when(mRaDaemon.start()).thenReturn(true);
- ArgumentCaptor<NeighborEventConsumer> neighborEventCaptor =
- ArgumentCaptor.forClass(NeighborEventConsumer.class);
- doReturn(mIpNeighborMonitor).when(mIpServerDeps).getIpNeighborMonitor(any(), any(),
- neighborEventCaptor.capture());
- final IpServer ipServer = new IpServer(
- interfaceName, mHandler, TETHERING_WIFI, new SharedLog("test"), mNetd,
- bpfCoordinator, mRoutingCoordinatorManager, mIpServerCallback, mTetherConfig,
- mAddressCoordinator, mTetheringMetrics, mIpServerDeps);
- ipServer.start();
- ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
- mTestLooper.dispatchAll();
-
- LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(UPSTREAM_IFACE);
- lp.setLinkAddresses(UPSTREAM_ADDRESSES);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, 0);
-
- mNeighborEventConsumer = neighborEventCaptor.getValue();
- return ipServer;
- }
-
- private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface,
- LinkProperties v6lp, int ttlAdjustment) {
- dispatchTetherConnectionChanged(ipServer, upstreamIface);
- ipServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, ttlAdjustment, 0, v6lp);
- mTestLooper.dispatchAll();
- }
-
- private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface) {
- final InterfaceSet ifs = (upstreamIface != null) ? new InterfaceSet(upstreamIface) : null;
- ipServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, ifs);
- mTestLooper.dispatchAll();
+ private void dispatchIpv6UpstreamChanged(BpfCoordinator bpfCoordinator, IpServer ipServer,
+ int upstreamIfindex, String upstreamIface, Set<IpPrefix> upstreamPrefixes) {
+ bpfCoordinator.maybeAddUpstreamToLookupTable(upstreamIfindex, upstreamIface);
+ bpfCoordinator.updateIpv6UpstreamInterface(ipServer, upstreamIfindex, upstreamPrefixes);
+ when(ipServer.getIpv6UpstreamIfindex()).thenReturn(upstreamIfindex);
+ when(ipServer.getIpv6UpstreamPrefixes()).thenReturn(upstreamPrefixes);
}
private void recvNewNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_NEWNEIGH, ifindex, addr,
nudState, mac));
- mTestLooper.dispatchAll();
}
private void recvDelNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_DELNEIGH, ifindex, addr,
nudState, mac));
- mTestLooper.dispatchAll();
}
@NonNull
private BpfCoordinator makeBpfCoordinator() throws Exception {
+ return makeBpfCoordinator(true /* addDefaultIpServer */);
+ }
+
+ @NonNull
+ private BpfCoordinator makeBpfCoordinator(boolean addDefaultIpServer) throws Exception {
// mStatsManager will be invoked twice if BpfCoordinator is created the second time.
clearInvocations(mStatsManager);
+ ArgumentCaptor<NeighborEventConsumer> neighborCaptor =
+ ArgumentCaptor.forClass(NeighborEventConsumer.class);
+ doReturn(mIpNeighborMonitor).when(mDeps).getIpNeighborMonitor(neighborCaptor.capture());
final BpfCoordinator coordinator = new BpfCoordinator(mDeps);
+ mNeighborEventConsumer = neighborCaptor.getValue();
+ assertNotNull(mNeighborEventConsumer);
mConsumer = coordinator.getBpfConntrackEventConsumerForTesting();
mTetherClients = coordinator.getTetherClientsForTesting();
@@ -681,6 +612,10 @@
mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder();
mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb);
+ if (addDefaultIpServer) {
+ coordinator.addIpServer(mIpServer);
+ }
+
return coordinator;
}
@@ -1008,7 +943,6 @@
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// InOrder is required because mBpfStatsMap may be accessed by both
// BpfCoordinator#tetherOffloadRuleAdd and BpfCoordinator#tetherOffloadGetAndClearStats.
@@ -1020,19 +954,18 @@
mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
final Ipv6DownstreamRule downstreamRule = buildTestDownstreamRule(
mobileIfIndex, NEIGH_A, MAC_A);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, upstreamRule);
- coordinator.addIpv6DownstreamRule(mIpServer, downstreamRule);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A);
verifyAddDownstreamRule(inOrder, downstreamRule);
// Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveDownstreamRule(inOrder, downstreamRule);
verifyRemoveUpstreamRule(inOrder, upstreamRule);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
@@ -1056,7 +989,6 @@
doReturn(usingApiS).when(mDeps).isAtLeastS();
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
@@ -1092,7 +1024,6 @@
setupFunctioningNetdInterface();
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
final String wlanIface = "wlan0";
final Integer wlanIfIndex = 100;
@@ -1148,7 +1079,7 @@
// [3] Stop coordinator.
// Shutdown the coordinator and clear the invocation history, especially the
// tetherOffloadGetStats() calls.
- coordinator.stopPolling();
+ coordinator.removeIpServer(mIpServer);
clearStatsInvocations();
// Verify the polling update thread stopped.
@@ -1162,7 +1093,6 @@
setupFunctioningNetdInterface();
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
@@ -1350,7 +1280,6 @@
final String mobileIface = "rmnet_data0";
final int mobileIfIndex = 100;
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// [1] Default limit.
// Set the unlimited quota as default if the service has never applied a data limit for a
@@ -1358,8 +1287,8 @@
final Ipv6UpstreamRule rule = buildTestUpstreamRule(
mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfLimitMap, mBpfStatsMap);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, rule);
@@ -1395,7 +1324,6 @@
final String mobileIface = "rmnet_data0";
final int mobileIfIndex = 100;
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// Applying a data limit to the current upstream does not take any immediate action.
// The data limit could be only set on an upstream which has rules.
@@ -1408,31 +1336,30 @@
// Adding the first rule on current upstream immediately sends the quota to BPF.
final Ipv6UpstreamRule ruleA = buildTestUpstreamRule(
mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, limit, true /* isInit */);
verifyAddUpstreamRule(inOrder, ruleA);
inOrder.verifyNoMoreInteractions();
// Adding the second rule on current upstream does not send the quota to BPF.
+ coordinator.addIpServer(mIpServer2);
final Ipv6UpstreamRule ruleB = buildTestUpstreamRule(
mobileIfIndex, DOWNSTREAM_IFINDEX2, UPSTREAM_PREFIX, DOWNSTREAM_MAC2);
- coordinator.updateAllIpv6Rules(
- mIpServer2, DOWNSTREAM_IFACE_PARAMS2, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer2, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyAddUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the second rule on current upstream does not send the quota to BPF.
- coordinator.updateAllIpv6Rules(
- mIpServer2, DOWNSTREAM_IFACE_PARAMS2, NO_UPSTREAM, NO_PREFIXES);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer2, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveUpstreamRule(inOrder, ruleA);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
inOrder.verifyNoMoreInteractions();
@@ -1448,8 +1375,6 @@
final String mobileIface = "rmnet_data0";
final Integer ethIfIndex = 100;
final Integer mobileIfIndex = 101;
- coordinator.maybeAddUpstreamToLookupTable(ethIfIndex, ethIface);
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map, mBpfLimitMap,
mBpfStatsMap);
@@ -1470,14 +1395,14 @@
final Ipv6DownstreamRule ethernetRuleB = buildTestDownstreamRule(
ethIfIndex, NEIGH_B, MAC_B);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, ethIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, ethIfIndex, ethIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, ethIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, ethernetUpstreamRule);
- coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleA);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A);
verifyAddDownstreamRule(inOrder, ethernetRuleA);
- coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleB);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_B, NUD_REACHABLE, MAC_B);
verifyAddDownstreamRule(inOrder, ethernetRuleB);
// [2] Update the existing rules from Ethernet to cellular.
@@ -1494,8 +1419,8 @@
// Update the existing rules for upstream changes. The rules are removed and re-added one
// by one for updating upstream interface index and prefixes by #tetherOffloadRuleUpdate.
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES2);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(inOrder, ethernetRuleA);
verifyRemoveDownstreamRule(inOrder, ethernetRuleB);
verifyRemoveUpstreamRule(inOrder, ethernetUpstreamRule);
@@ -1532,7 +1457,6 @@
// #makeBpfCoordinator for testing.
// See #testBpfDisabledbyNoBpfDownstream6Map.
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
// The tether stats polling task should not be scheduled.
mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
@@ -1549,7 +1473,7 @@
final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1");
final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a");
final Ipv6DownstreamRule rule = buildTestDownstreamRule(ifIndex, neigh, mac);
- coordinator.addIpv6DownstreamRule(mIpServer, rule);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, neigh, NUD_REACHABLE, mac);
verifyNeverAddDownstreamRule();
LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules =
coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
@@ -1561,7 +1485,7 @@
rules = new LinkedHashMap<Inet6Address, Ipv6DownstreamRule>();
rules.put(rule.address, rule);
coordinator.getIpv6DownstreamRulesForTesting().put(mIpServer, rules);
- coordinator.removeIpv6DownstreamRule(mIpServer, rule);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, neigh, NUD_STALE, mac);
verifyNeverRemoveDownstreamRule();
rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
assertNotNull(rules);
@@ -1575,8 +1499,8 @@
assertEquals(1, rules.size());
// The rule can't be updated.
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS,
- rule.upstreamIfindex + 1 /* new */, UPSTREAM_PREFIXES);
+ coordinator.updateIpv6UpstreamInterface(mIpServer, rule.upstreamIfindex + 1 /* new */,
+ UPSTREAM_PREFIXES);
verifyNeverRemoveDownstreamRule();
verifyNeverAddDownstreamRule();
rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
@@ -1753,18 +1677,14 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
// [1] The default polling interval.
- coordinator.startPolling();
assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
- coordinator.stopPolling();
// [2] Expect the invalid polling interval isn't applied. The valid range of interval is
// DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long.
for (final int interval
: new int[] {0, 100, DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS - 1}) {
when(mTetherConfig.getOffloadPollInterval()).thenReturn(interval);
- coordinator.startPolling();
assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
- coordinator.stopPolling();
}
// [3] Set a specific polling interval which is larger than default value.
@@ -1772,7 +1692,6 @@
// approximation is used to verify the scheduled time of the polling thread.
final int pollingInterval = 100_000;
when(mTetherConfig.getOffloadPollInterval()).thenReturn(pollingInterval);
- coordinator.startPolling();
// Expect the specific polling interval to be applied.
assertEquals(pollingInterval, coordinator.getPollingInterval());
@@ -1800,19 +1719,19 @@
public void testStartStopConntrackMonitoring() throws Exception {
setupFunctioningNetdInterface();
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ final BpfCoordinator coordinator = makeBpfCoordinator(false /* addDefaultIpServer */);
// [1] Don't stop monitoring if it has never started.
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor, never()).stop();
// [2] Start monitoring.
- coordinator.startMonitoring(mIpServer);
+ coordinator.addIpServer(mIpServer);
verify(mConntrackMonitor).start();
clearInvocations(mConntrackMonitor);
// [3] Stop monitoring.
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor).stop();
}
@@ -1823,12 +1742,12 @@
public void testStartStopConntrackMonitoring_R() throws Exception {
setupFunctioningNetdInterface();
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ final BpfCoordinator coordinator = makeBpfCoordinator(false /* addDefaultIpServer */);
- coordinator.startMonitoring(mIpServer);
+ coordinator.addIpServer(mIpServer);
verify(mConntrackMonitor, never()).start();
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor, never()).stop();
}
@@ -1837,23 +1756,23 @@
public void testStartStopConntrackMonitoringWithTwoDownstreamIfaces() throws Exception {
setupFunctioningNetdInterface();
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ final BpfCoordinator coordinator = makeBpfCoordinator(false /* addDefaultIpServer */);
// [1] Start monitoring at the first IpServer adding.
- coordinator.startMonitoring(mIpServer);
+ coordinator.addIpServer(mIpServer);
verify(mConntrackMonitor).start();
clearInvocations(mConntrackMonitor);
// [2] Don't start monitoring at the second IpServer adding.
- coordinator.startMonitoring(mIpServer2);
+ coordinator.addIpServer(mIpServer2);
verify(mConntrackMonitor, never()).start();
// [3] Don't stop monitoring if any downstream interface exists.
- coordinator.stopMonitoring(mIpServer2);
+ coordinator.removeIpServer(mIpServer2);
verify(mConntrackMonitor, never()).stop();
// [4] Stop monitoring if no downstream exists.
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor).stop();
}
@@ -2016,9 +1935,8 @@
public void testAddDevMapRule6() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
eq(new TetherDevValue(UPSTREAM_IFINDEX)));
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)),
@@ -2027,8 +1945,9 @@
// Adding the second downstream, only the second downstream ifindex is added to DevMap,
// the existing upstream ifindex won't be added again.
- coordinator.updateAllIpv6Rules(
- mIpServer2, DOWNSTREAM_IFACE_PARAMS2, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ coordinator.addIpServer(mIpServer2);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer2, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX2)),
eq(new TetherDevValue(DOWNSTREAM_IFINDEX2)));
verify(mBpfDevMap, never()).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
@@ -2087,7 +2006,6 @@
.startMocking();
try {
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
bpfMap.insertEntry(tcpKey, tcpValue);
bpfMap.insertEntry(udpKey, udpValue);
@@ -2116,7 +2034,7 @@
ExtendedMockito.clearInvocations(staticMockMarker(NetlinkUtils.class));
// [3] Don't refresh conntrack timeout if polling stopped.
- coordinator.stopPolling();
+ coordinator.removeIpServer(mIpServer);
mTestLooper.moveTimeForward(CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
waitForIdle();
ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkUtils.class));
@@ -2855,8 +2773,9 @@
final int myIfindex = DOWNSTREAM_IFINDEX;
final int notMyIfindex = myIfindex - 1;
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
resetNetdAndBpfMaps();
verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
@@ -2905,10 +2824,8 @@
resetNetdAndBpfMaps();
InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
- LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(UPSTREAM_IFACE2);
- lp.setLinkAddresses(UPSTREAM_ADDRESSES);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp, -1);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, UPSTREAM_PREFIXES);
final Ipv6DownstreamRule ruleA2 = buildTestDownstreamRule(
UPSTREAM_IFINDEX2, NEIGH_A, MAC_A);
final Ipv6DownstreamRule ruleB2 = buildTestDownstreamRule(
@@ -2922,11 +2839,9 @@
verifyNoUpstreamIpv6ForwardingChange(inOrder);
resetNetdAndBpfMaps();
- // Upstream link addresses change result in updating the rules.
- LinkProperties lp2 = new LinkProperties();
- lp2.setInterfaceName(UPSTREAM_IFACE2);
- lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, -1);
+ // Upstream prefixes change result in updating the rules.
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(inOrder, ruleA2);
verifyRemoveDownstreamRule(inOrder, ruleB2);
verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES);
@@ -2936,7 +2851,7 @@
resetNetdAndBpfMaps();
// When the upstream is lost, rules are removed.
- dispatchTetherConnectionChanged(ipServer, null, null, 0);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(ruleA2);
verifyRemoveDownstreamRule(ruleB2);
@@ -2947,7 +2862,7 @@
resetNetdAndBpfMaps();
// If the upstream is IPv4-only, no IPv6 rules are added to BPF map.
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
resetNetdAndBpfMaps();
recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A);
verifyNoUpstreamIpv6ForwardingChange(null);
@@ -2957,8 +2872,8 @@
// Rules can be added again once upstream IPv6 connectivity is available. The existing rules
// with an upstream of NO_UPSTREAM are reapplied.
- lp.setInterfaceName(UPSTREAM_IFACE);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, -1);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verifyAddDownstreamRule(ruleA);
recvNewNeigh(myIfindex, NEIGH_B, NUD_REACHABLE, MAC_B);
@@ -2966,23 +2881,27 @@
// If upstream IPv6 connectivity is lost, rules are removed.
resetNetdAndBpfMaps();
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, null, 0);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveDownstreamRule(ruleA);
verifyRemoveDownstreamRule(ruleB);
verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES);
// When upstream IPv6 connectivity comes back, upstream rules are added and downstream rules
// are reapplied.
- lp.setInterfaceName(UPSTREAM_IFACE);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, -1);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verifyAddDownstreamRule(ruleA);
verifyAddDownstreamRule(ruleB);
resetNetdAndBpfMaps();
// When the downstream interface goes down, rules are removed.
- ipServer.stop();
- mTestLooper.dispatchAll();
+ // Simulate receiving CMD_INTERFACE_DOWN in the BaseServingState of IpServer.
+ reset(mIpNeighborMonitor);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
+ coordinator.tetherOffloadClientClear(mIpServer);
+ coordinator.removeIpServer(mIpServer);
+
verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES);
verifyRemoveDownstreamRule(ruleA);
verifyRemoveDownstreamRule(ruleB);
@@ -3004,7 +2923,8 @@
// [1] Enable BPF offload.
// A neighbor that is added or deleted causes the rule to be added or removed.
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
resetNetdAndBpfMaps();
recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A);
@@ -3019,10 +2939,8 @@
resetNetdAndBpfMaps();
// Upstream IPv6 connectivity change causes upstream rules change.
- LinkProperties lp2 = new LinkProperties();
- lp2.setInterfaceName(UPSTREAM_IFACE2);
- lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, 0);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, UPSTREAM_PREFIXES2);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
resetNetdAndBpfMaps();
@@ -3030,7 +2948,6 @@
// A neighbor that is added or deleted doesn’t cause the rule to be added or removed.
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false);
final BpfCoordinator coordinator2 = makeBpfCoordinator();
- final IpServer ipServer2 = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator2);
verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdAndBpfMaps();
@@ -3043,7 +2960,8 @@
resetNetdAndBpfMaps();
// Upstream IPv6 connectivity change doesn't cause the rule to be added or removed.
- dispatchTetherConnectionChanged(ipServer2, UPSTREAM_IFACE2, lp2, 0);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer2, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, NO_PREFIXES);
verifyNoUpstreamIpv6ForwardingChange(null);
verifyNeverRemoveDownstreamRule();
resetNetdAndBpfMaps();
@@ -3053,7 +2971,6 @@
public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception {
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false);
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
// IP neighbor monitor doesn't start if BPF offload is disabled.
verify(mIpNeighborMonitor, never()).start();
@@ -3062,15 +2979,10 @@
@Test
public void testSkipVirtualNetworkInBpf() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
- final LinkProperties v6Only = new LinkProperties();
- v6Only.setInterfaceName(IPSEC_IFACE);
- v6Only.setLinkAddresses(UPSTREAM_ADDRESSES);
resetNetdAndBpfMaps();
- dispatchTetherConnectionChanged(ipServer, IPSEC_IFACE, v6Only, 0);
- verify(mNetd).tetherAddForward(DOWNSTREAM_IFACE, IPSEC_IFACE);
- verify(mNetd).ipfwdAddInterfaceForward(DOWNSTREAM_IFACE, IPSEC_IFACE);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, IPSEC_IFINDEX, IPSEC_IFACE, UPSTREAM_PREFIXES);
verifyNeverAddUpstreamRule();
recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A);
@@ -3080,7 +2992,6 @@
@Test
public void addRemoveTetherClient() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
final int myIfindex = DOWNSTREAM_IFINDEX;
final int notMyIfindex = myIfindex - 1;
@@ -3089,33 +3000,36 @@
final InetAddress neighLL = InetAddresses.parseNumericAddress("169.254.0.1");
final InetAddress neighMC = InetAddresses.parseNumericAddress("224.0.0.1");
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
+
// Events on other interfaces are ignored.
recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, MAC_A);
- assertNull(mTetherClients.get(ipServer));
+ assertNull(mTetherClients.get(mIpServer));
// Events on this interface are received and sent to BpfCoordinator.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, MAC_A);
- assertClientInfoExists(ipServer,
+ assertClientInfoExists(mIpServer,
new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighA, MAC_A));
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, MAC_B);
- assertClientInfoExists(ipServer,
+ assertClientInfoExists(mIpServer,
new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighB, MAC_B));
// Link-local and multicast neighbors are ignored.
recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, MAC_A);
- assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighLL);
+ assertClientInfoDoesNotExist(mIpServer, (Inet4Address) neighLL);
recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, MAC_A);
- assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighMC);
+ assertClientInfoDoesNotExist(mIpServer, (Inet4Address) neighMC);
// A neighbor that is no longer valid causes the client to be removed.
// NUD_FAILED events do not have a MAC address.
recvNewNeigh(myIfindex, neighA, NUD_FAILED, null);
- assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighA);
+ assertClientInfoDoesNotExist(mIpServer, (Inet4Address) neighA);
// A neighbor that is deleted causes the client to be removed.
recvDelNeigh(myIfindex, neighB, NUD_STALE, MAC_B);
// When last client information is deleted, IpServer will be removed from mTetherClients
- assertNull(mTetherClients.get(ipServer));
+ assertNull(mTetherClients.get(mIpServer));
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 750bfce..9f430af 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -3622,6 +3622,42 @@
InetAddresses.parseNumericAddress(ifaceConfig.ipv4Addr), ifaceConfig.prefixLength);
assertFalse(sapPrefix.equals(lohsPrefix));
}
+
+ @Test
+ public void testWifiTetheringWhenP2pActive() throws Exception {
+ initTetheringOnTestThread();
+ // Enable wifi P2P.
+ sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
+ verifyInterfaceServingModeStarted(TEST_P2P_IFNAME);
+ verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER);
+ verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
+ verify(mUpstreamNetworkMonitor).startObserveAllNetworks();
+ // Verify never enable upstream if only P2P active.
+ verify(mUpstreamNetworkMonitor, never()).setTryCell(true);
+ assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastErrorForTest(TEST_P2P_IFNAME));
+
+ when(mWifiManager.startTetheredHotspot(any())).thenReturn(true);
+ // Emulate pressing the WiFi tethering button.
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
+ mLooper.dispatchAll();
+ verify(mWifiManager).startTetheredHotspot(null);
+ verifyNoMoreInteractions(mWifiManager);
+
+ mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
+ verify(mWifiManager).updateInterfaceIpState(
+ TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+
+ verify(mWifiManager).updateInterfaceIpState(TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ verifyNoMoreInteractions(mWifiManager);
+ verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER);
+
+ verify(mUpstreamNetworkMonitor).setTryCell(true);
+ }
+
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
index f8e98e3..2417385 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
@@ -19,9 +19,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.State
+import com.android.net.module.util.SyncStateMachine
+import com.android.net.module.util.SyncStateMachine.StateInfo
import com.android.networkstack.tethering.util.StateMachineShim.AsyncStateMachine
import com.android.networkstack.tethering.util.StateMachineShim.Dependencies
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 674cd98..1958aa8 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -94,13 +94,13 @@
}
bpf {
- name: "offload@btf.o",
- srcs: ["offload@btf.c"],
+ name: "offload@mainline.o",
+ srcs: ["offload@mainline.c"],
btf: true,
cflags: [
"-Wall",
"-Werror",
- "-DBTF",
+ "-DMAINLINE",
],
}
@@ -114,13 +114,13 @@
}
bpf {
- name: "test@btf.o",
- srcs: ["test@btf.c"],
+ name: "test@mainline.o",
+ srcs: ["test@mainline.c"],
btf: true,
cflags: [
"-Wall",
"-Werror",
- "-DBTF",
+ "-DMAINLINE",
],
}
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index 0a2b0b8..152dda6 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -19,8 +19,8 @@
#include <netinet/in.h>
#include <stdint.h>
-// The resulting .o needs to load on the Android T bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+// The resulting .o needs to load on Android T+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include "bpf_helpers.h"
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index f3c7de5..1511ee5 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -35,6 +35,7 @@
// this returns 0 iff skb->sk is NULL
static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_cookie;
+static uint64_t (*bpf_get_sk_cookie)(struct bpf_sock* sk) = (void*)BPF_FUNC_get_socket_cookie;
static uint32_t (*bpf_get_socket_uid)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_uid;
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index addb02f..f83e5ae 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -30,8 +30,8 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
-// The resulting .o needs to load on the Android T bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+// The resulting .o needs to load on Android T+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
@@ -265,6 +265,10 @@
*(struct iphdr*)data = ip;
}
+ // Count successfully translated packet
+ __sync_fetch_and_add(&v->packets, 1);
+ __sync_fetch_and_add(&v->bytes, skb->len - l2_header_size);
+
// Redirect, possibly back to same interface, so tcpdump sees packet twice.
if (v->oif) return bpf_redirect(v->oif, BPF_F_INGRESS);
@@ -416,6 +420,10 @@
// Copy over the new ipv6 header without an ethernet header.
*(struct ipv6hdr*)data = ip6;
+ // Count successfully translated packet
+ __sync_fetch_and_add(&v->packets, 1);
+ __sync_fetch_and_add(&v->bytes, skb->len);
+
// Redirect to non v4-* interface. Tcpdump only sees packet after this redirect.
return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
}
diff --git a/bpf_progs/clatd.h b/bpf_progs/clatd.h
index b5f1cdc..a75798f 100644
--- a/bpf_progs/clatd.h
+++ b/bpf_progs/clatd.h
@@ -39,8 +39,10 @@
typedef struct {
uint32_t oif; // The output interface to redirect to (0 means don't redirect)
struct in_addr local4; // The destination IPv4 address
+ uint64_t packets; // Count of translated gso (large) packets
+ uint64_t bytes; // Sum of post-translation skb->len
} ClatIngress6Value;
-STRUCT_SIZE(ClatIngress6Value, 4 + 4); // 8
+STRUCT_SIZE(ClatIngress6Value, 4 + 4 + 8 + 8); // 24
typedef struct {
uint32_t iif; // The input interface index
@@ -54,7 +56,9 @@
struct in6_addr pfx96; // The destination /96 nat64 prefix, bottom 32 bits must be 0
bool oifIsEthernet; // Whether the output interface requires ethernet header
uint8_t pad[3];
+ uint64_t packets; // Count of translated gso (large) packets
+ uint64_t bytes; // Sum of post-translation skb->len
} ClatEgress4Value;
-STRUCT_SIZE(ClatEgress4Value, 4 + 2 * 16 + 1 + 3); // 40
+STRUCT_SIZE(ClatEgress4Value, 4 + 2 * 16 + 1 + 3 + 8 + 8); // 56
#undef STRUCT_SIZE
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index e845a69..ed114e4 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -27,8 +27,8 @@
#include <stdint.h>
#include <string.h>
-// The resulting .o needs to load on the Android T bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+// The resulting .o needs to load on Android T+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include "bpf_helpers.h"
#include "dscpPolicy.h"
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 5e401aa..b3cde45 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-// The resulting .o needs to load on the Android T bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+// The resulting .o needs to load on Android T+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include <bpf_helpers.h>
#include <linux/bpf.h>
@@ -97,19 +97,22 @@
DEFINE_BPF_MAP_NO_NETD(ingress_discard_map, HASH, IngressDiscardKey, IngressDiscardValue,
INGRESS_DISCARD_MAP_SIZE)
+DEFINE_BPF_MAP_RW_NETD(lock_array_test_map, ARRAY, uint32_t, bool, 1)
+DEFINE_BPF_MAP_RW_NETD(lock_hash_test_map, HASH, uint32_t, bool, 1)
+
/* never actually used from ebpf */
DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE)
// A single-element configuration array, packet tracing is enabled when 'true'.
DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG)
// A ring buffer on which packet information is pushed.
DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG);
DEFINE_BPF_MAP_RO_NETD(data_saver_enabled_map, ARRAY, uint32_t, bool,
@@ -139,6 +142,11 @@
#define DEFINE_NETD_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
DEFINE_NETD_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE)
+#define DEFINE_NETD_V_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV) \
+ DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV, \
+ KVER_INF, BPFLOADER_MAINLINE_V_VERSION, BPFLOADER_MAX_VER, MANDATORY, \
+ "fs_bpf_netd_readonly", "", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
+
// programs that only need to be usable by the system server
#define DEFINE_SYS_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE, KVER_INF, \
@@ -407,6 +415,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;
@@ -516,7 +527,7 @@
// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace_user", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
"fs_bpf_netd_readonly", "",
IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -526,7 +537,7 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace, KVER_5_8, KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -548,7 +559,7 @@
// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace_user", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
"fs_bpf_netd_readonly", "",
IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -558,7 +569,7 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace, KVER_5_8, KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -644,7 +655,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;
}
@@ -662,13 +674,86 @@
return permissions ? *permissions : BPF_PERMISSION_INTERNET;
}
-DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
+DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet_create", AID_ROOT, AID_ROOT, inet_socket_create,
KVER_4_14)
(struct bpf_sock* sk) {
// A return value of 1 means allow, everything else means deny.
return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? 1 : 0;
}
+DEFINE_NETD_V_BPF_PROG_KVER("cgroupsockrelease/inet_release", AID_ROOT, AID_ROOT,
+ inet_socket_release, KVER_5_10)
+(struct bpf_sock* sk) {
+ uint64_t cookie = bpf_get_sk_cookie(sk);
+ if (cookie) bpf_cookie_tag_map_delete_elem(&cookie);
+
+ return 1;
+}
+
+static __always_inline inline int check_localhost(struct bpf_sock_addr *ctx) {
+ // See include/uapi/linux/bpf.h:
+ //
+ // struct bpf_sock_addr {
+ // __u32 user_family; // R: 4 byte
+ // __u32 user_ip4; // BE, R: 1,2,4-byte, W: 4-byte
+ // __u32 user_ip6[4]; // BE, R: 1,2,4,8-byte, W: 4,8-byte
+ // __u32 user_port; // BE, R: 1,2,4-byte, W: 4-byte
+ // __u32 family; // R: 4 byte
+ // __u32 type; // R: 4 byte
+ // __u32 protocol; // R: 4 byte
+ // __u32 msg_src_ip4; // BE, R: 1,2,4-byte, W: 4-byte
+ // __u32 msg_src_ip6[4]; // BE, R: 1,2,4,8-byte, W: 4,8-byte
+ // __bpf_md_ptr(struct bpf_sock *, sk);
+ // };
+ return 1;
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("connect4/inet4_connect", AID_ROOT, AID_ROOT, inet4_connect, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("connect6/inet6_connect", AID_ROOT, AID_ROOT, inet6_connect, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("recvmsg4/udp4_recvmsg", AID_ROOT, AID_ROOT, udp4_recvmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("recvmsg6/udp6_recvmsg", AID_ROOT, AID_ROOT, udp6_recvmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("sendmsg4/udp4_sendmsg", AID_ROOT, AID_ROOT, udp4_sendmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("sendmsg6/udp6_sendmsg", AID_ROOT, AID_ROOT, udp6_sendmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("getsockopt/prog", AID_ROOT, AID_ROOT, getsockopt_prog, KVER_5_4)
+(struct bpf_sockopt *ctx) {
+ // Tell kernel to return 'original' kernel reply (instead of the bpf modified buffer)
+ // This is important if the answer is larger than PAGE_SIZE (max size this bpf hook can provide)
+ ctx->optlen = 0;
+ return 1; // ALLOW
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("setsockopt/prog", AID_ROOT, AID_ROOT, setsockopt_prog, KVER_5_4)
+(struct bpf_sockopt *ctx) {
+ // Tell kernel to use/process original buffer provided by userspace.
+ // This is important if it is larger than PAGE_SIZE (max size this bpf hook can handle).
+ ctx->optlen = 0;
+ return 1; // ALLOW
+}
+
LICENSE("Apache 2.0");
CRITICAL("Connectivity and netd");
DISABLE_BTF_ON_USER_BUILDS();
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 098147f..4877a4b 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -155,7 +155,16 @@
ASSERT_STRING_EQUAL(XT_BPF_ALLOWLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilter_allowlist_xtbpf");
ASSERT_STRING_EQUAL(XT_BPF_DENYLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilter_denylist_xtbpf");
-#define CGROUP_SOCKET_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
+#define CGROUP_INET_CREATE_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
+#define CGROUP_INET_RELEASE_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsockrelease_inet_release"
+#define CGROUP_CONNECT4_PROG_PATH BPF_NETD_PATH "prog_netd_connect4_inet4_connect"
+#define CGROUP_CONNECT6_PROG_PATH BPF_NETD_PATH "prog_netd_connect6_inet6_connect"
+#define CGROUP_UDP4_RECVMSG_PROG_PATH BPF_NETD_PATH "prog_netd_recvmsg4_udp4_recvmsg"
+#define CGROUP_UDP6_RECVMSG_PROG_PATH BPF_NETD_PATH "prog_netd_recvmsg6_udp6_recvmsg"
+#define CGROUP_UDP4_SENDMSG_PROG_PATH BPF_NETD_PATH "prog_netd_sendmsg4_udp4_sendmsg"
+#define CGROUP_UDP6_SENDMSG_PROG_PATH BPF_NETD_PATH "prog_netd_sendmsg6_udp6_sendmsg"
+#define CGROUP_GETSOCKOPT_PROG_PATH BPF_NETD_PATH "prog_netd_getsockopt_prog"
+#define CGROUP_SETSOCKOPT_PROG_PATH BPF_NETD_PATH "prog_netd_setsockopt_prog"
#define TC_BPF_INGRESS_ACCOUNT_PROG_NAME "prog_netd_schedact_ingress_account"
#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_NETD_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
@@ -181,7 +190,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 +201,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)
@@ -260,5 +270,5 @@
static inline bool is_system_uid(uint32_t uid) {
// MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0
// MAX_SYSTEM_UID is AID_NOBODY == 9999, while AID_APP_START == 10000
- return (uid < AID_APP_START);
+ return ((uid % AID_USER_OFFSET) < AID_APP_START);
}
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 90f96a1..4f152bf 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -24,16 +24,16 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
-#ifdef BTF
+#ifdef MAINLINE
// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
// ship a different file than for later versions, but we need bpfloader v0.25+
// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
-#else /* BTF */
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#else /* MAINLINE */
// The resulting .o needs to load on the Android S bpfloader
#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
-#endif /* BTF */
+#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
+#endif /* MAINLINE */
// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
#define TETHERING_UID AID_ROOT
diff --git a/bpf_progs/offload@btf.c b/bpf_progs/offload@mainline.c
similarity index 100%
rename from bpf_progs/offload@btf.c
rename to bpf_progs/offload@mainline.c
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index 70b08b7..6a4471c 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -18,16 +18,16 @@
#include <linux/in.h>
#include <linux/ip.h>
-#ifdef BTF
+#ifdef MAINLINE
// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
// ship a different file than for later versions, but we need bpfloader v0.25+
// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
-#else /* BTF */
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#else /* MAINLINE */
// The resulting .o needs to load on the Android S bpfloader
#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
-#endif /* BTF */
+#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
+#endif /* MAINLINE */
// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
#define TETHERING_UID AID_ROOT
@@ -45,6 +45,10 @@
// Used only by TetheringPrivilegedTests, not by production code.
DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
TETHERING_GID)
+DEFINE_BPF_MAP_GRW(tether2_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
+ TETHERING_GID)
+DEFINE_BPF_MAP_GRW(tether3_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
+ TETHERING_GID)
// Used only by BpfBitmapTest, not by production code.
DEFINE_BPF_MAP_GRW(bitmap, ARRAY, int, uint64_t, 2, TETHERING_GID)
diff --git a/bpf_progs/test@btf.c b/bpf_progs/test@mainline.c
similarity index 100%
rename from bpf_progs/test@btf.c
rename to bpf_progs/test@mainline.c
diff --git a/common/Android.bp b/common/Android.bp
index 0048a0a..5fabf41 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -26,7 +26,7 @@
// as the above target may not exist
// depending on the branch
-// The library requires the final artifact to contain net-utils-device-common-struct.
+// The library requires the final artifact to contain net-utils-device-common-struct-base.
java_library {
name: "connectivity-net-module-utils-bpf",
srcs: [
@@ -45,7 +45,7 @@
// For libraries which are statically linked in framework-connectivity, do not
// statically link here because callers of this library might already have a static
// version linked.
- "net-utils-device-common-struct",
+ "net-utils-device-common-struct-base",
],
apex_available: [
"com.android.tethering",
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 19b522c..b320b61 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -1,11 +1,12 @@
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
flag {
name: "set_data_saver_via_cm"
+ is_exported: true
namespace: "android_core_networking"
description: "Set data saver through ConnectivityManager API"
bug: "297836825"
@@ -13,6 +14,7 @@
flag {
name: "support_is_uid_networking_blocked"
+ is_exported: true
namespace: "android_core_networking"
description: "This flag controls whether isUidNetworkingBlocked is supported"
bug: "297836825"
@@ -20,6 +22,7 @@
flag {
name: "basic_background_restrictions_enabled"
+ is_exported: true
namespace: "android_core_networking"
description: "Block network access for apps in a low importance background state"
bug: "304347838"
@@ -27,6 +30,7 @@
flag {
name: "ipsec_transform_state"
+ is_exported: true
namespace: "android_core_networking_ipsec"
description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
bug: "308011229"
@@ -34,6 +38,7 @@
flag {
name: "tethering_request_with_soft_ap_config"
+ is_exported: true
namespace: "android_core_networking"
description: "The flag controls the access for the parcelable TetheringRequest with getSoftApConfiguration/setSoftApConfiguration API"
bug: "216524590"
@@ -41,6 +46,7 @@
flag {
name: "request_restricted_wifi"
+ is_exported: true
namespace: "android_core_networking"
description: "Flag for API to support requesting restricted wifi"
bug: "315835605"
@@ -48,6 +54,7 @@
flag {
name: "net_capability_local_network"
+ is_exported: true
namespace: "android_core_networking"
description: "Flag for local network capability API"
bug: "313000440"
@@ -55,6 +62,7 @@
flag {
name: "support_transport_satellite"
+ is_exported: true
namespace: "android_core_networking"
description: "Flag for satellite transport API"
bug: "320514105"
@@ -62,6 +70,7 @@
flag {
name: "nsd_subtypes_support_enabled"
+ is_exported: true
namespace: "android_core_networking"
description: "Flag for API to support nsd subtypes"
bug: "265095929"
@@ -69,7 +78,48 @@
flag {
name: "register_nsd_offload_engine_api"
+ is_exported: true
namespace: "android_core_networking"
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"
+}
+
+flag {
+ name: "net_capability_not_bandwidth_constrained"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED API"
+ bug: "343823469"
+}
+
+flag {
+ name: "tethering_request_virtual"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for introducing TETHERING_VIRTUAL type"
+ bug: "340376953"
+}
diff --git a/common/nearby_flags.aconfig b/common/nearby_flags.aconfig
index b957d33..55a865b 100644
--- a/common/nearby_flags.aconfig
+++ b/common/nearby_flags.aconfig
@@ -1,8 +1,9 @@
package: "com.android.nearby.flags"
-container: "system"
+container: "com.android.tethering"
flag {
name: "powered_off_finding"
+ is_exported: true
namespace: "nearby"
description: "Controls whether the Powered Off Finding feature is enabled"
bug: "307898240"
diff --git a/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java b/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
index 69fab09..71f7516 100644
--- a/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
+++ b/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
@@ -36,11 +36,24 @@
@Field(order = 3, type = Type.U8, padding = 3)
public final short oifIsEthernet; // Whether the output interface requires ethernet header
+ @Field(order = 4, type = Type.U63)
+ public final long packets; // Count of translated gso (large) packets
+
+ @Field(order = 5, type = Type.U63)
+ public final long bytes; // Sum of post-translation skb->len
+
public ClatEgress4Value(final int oif, final Inet6Address local6, final Inet6Address pfx96,
- final short oifIsEthernet) {
+ final short oifIsEthernet, final long packets, final long bytes) {
this.oif = oif;
this.local6 = local6;
this.pfx96 = pfx96;
this.oifIsEthernet = oifIsEthernet;
+ this.packets = packets;
+ this.bytes = bytes;
+ }
+
+ public ClatEgress4Value(final int oif, final Inet6Address local6, final Inet6Address pfx96,
+ final short oifIsEthernet) {
+ this(oif, local6, pfx96, oifIsEthernet, 0, 0);
}
}
diff --git a/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java b/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
index fb81caa..25f737b 100644
--- a/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
+++ b/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
@@ -30,8 +30,21 @@
@Field(order = 1, type = Type.Ipv4Address)
public final Inet4Address local4; // The destination IPv4 address
- public ClatIngress6Value(final int oif, final Inet4Address local4) {
+ @Field(order = 2, type = Type.U63)
+ public final long packets; // Count of translated gso (large) packets
+
+ @Field(order = 3, type = Type.U63)
+ public final long bytes; // Sum of post-translation skb->len
+
+ public ClatIngress6Value(final int oif, final Inet4Address local4, final long packets,
+ final long bytes) {
this.oif = oif;
this.local4 = local4;
+ this.packets = packets;
+ this.bytes = bytes;
+ }
+
+ public ClatIngress6Value(final int oif, final Inet4Address local4) {
+ this(oif, local4, 0, 0);
}
}
diff --git a/common/thread_flags.aconfig b/common/thread_flags.aconfig
index 09595a6..43acd1b 100644
--- a/common/thread_flags.aconfig
+++ b/common/thread_flags.aconfig
@@ -1,8 +1,9 @@
package: "com.android.net.thread.flags"
-container: "system"
+container: "com.android.tethering"
flag {
name: "thread_enabled"
+ is_exported: true
namespace: "thread_network"
description: "Controls whether the Android Thread feature is enabled"
bug: "301473012"
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index bc919ac..f076f5b 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -201,6 +201,9 @@
"com.android.net.thread.flags-aconfig",
"nearby_flags",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// This rule is not used anymore(b/268440216).
diff --git a/framework-t/lint-baseline.xml b/framework-t/lint-baseline.xml
new file mode 100644
index 0000000..4e206ed
--- /dev/null
+++ b/framework-t/lint-baseline.xml
@@ -0,0 +1,1313 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="Field `SERVICE_NAME` is a flagged API and should be inside an `if (ThreadNetworkFlags.threadEnabled())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" ThreadNetworkManager.SERVICE_NAME,"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java"
+ line="101"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ThreadNetworkManager` is a flagged API and should be inside an `if (ThreadNetworkFlags.threadEnabled())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" ThreadNetworkManager.class,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java"
+ line="102"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ThreadNetworkManager()` is a flagged API and should be inside an `if (ThreadNetworkFlags.threadEnabled())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" return new ThreadNetworkManager(context, managerService);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java"
+ line="106"
+ column="28"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="529"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="573"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="605"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="This is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `handleMessage` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" final String s = getNsdServiceInfoType((DiscoveryRequest) obj);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1076"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getServiceType()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `getNsdServiceInfoType` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" return r.getServiceType();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1236"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1477"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)"
+ errorLine2=" ^">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1477"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setNetwork()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)"
+ errorLine2=" ^">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1477"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `discoverServices()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" discoverServices(request, executor, listener);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1479"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" new DiscoveryRequest.Builder(protocolType, serviceType).build();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1566"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" new DiscoveryRequest.Builder(protocolType, serviceType).build();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1566"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getSubtypes()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `NsdServiceInfo` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" mSubtypes = new ArraySet<>(other.getSubtypes());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdServiceInfo.java"
+ line="106"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setSubtypes()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" info.setSubtypes(new ArraySet<>(in.createStringArrayList()));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdServiceInfo.java"
+ line="673"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+</issues>
diff --git a/framework-t/src/android/app/usage/NetworkStatsManager.java b/framework-t/src/android/app/usage/NetworkStatsManager.java
index 7fa0661..18c839f 100644
--- a/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -752,8 +752,8 @@
/**
* Query realtime mobile network usage statistics.
*
- * Return a snapshot of current UID network statistics, as it applies
- * to the mobile radios of the device. The snapshot will include any
+ * Return a snapshot of current UID network statistics for both cellular and satellite (which
+ * also uses same mobile radio as cellular) when called. The snapshot will include any
* tethering traffic, video calling data usage and count of
* network operations set by {@link TrafficStats#incrementOperationCount}
* made over a mobile radio.
diff --git a/framework-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java
index b8070f0..719f60d 100644
--- a/framework-t/src/android/net/EthernetManager.java
+++ b/framework-t/src/android/net/EthernetManager.java
@@ -642,7 +642,14 @@
}
/**
- * Listen to changes in the state of ethernet.
+ * Register a IntConsumer to be called back on ethernet state changes.
+ *
+ * <p>{@link IntConsumer#accept} with the current ethernet state will be triggered immediately
+ * upon adding a listener. The same callback is invoked on Ethernet state change, i.e. when
+ * calling {@link #setEthernetEnabled}.
+ * <p>The reported state is represented by:
+ * {@link #ETHERNET_STATE_DISABLED}: ethernet is now disabled.
+ * {@link #ETHERNET_STATE_ENABLED}: ethernet is now enabled.
*
* @param executor to run callbacks on.
* @param listener to listen ethernet state changed.
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/AdvertisingRequest.java b/framework-t/src/android/net/nsd/AdvertisingRequest.java
index 2895b0c..6afb2d5 100644
--- a/framework-t/src/android/net/nsd/AdvertisingRequest.java
+++ b/framework-t/src/android/net/nsd/AdvertisingRequest.java
@@ -110,8 +110,9 @@
}
/**
- * Returns the time interval that the resource records may be cached on a DNS resolver or
- * {@code null} if not specified.
+ * Returns the time interval that the resource records may be cached on a DNS resolver.
+ *
+ * The value will be {@code null} if it's not specified with the {@link #Builder}.
*
* @hide
*/
@@ -161,7 +162,7 @@
dest.writeParcelable(mServiceInfo, flags);
dest.writeInt(mProtocolType);
dest.writeLong(mAdvertisingConfig);
- dest.writeLong(mTtl == null ? -1 : mTtl.getSeconds());
+ dest.writeLong(mTtl == null ? -1L : mTtl.getSeconds());
}
// @FlaggedApi(NsdManager.Flags.ADVERTISE_REQUEST_API)
@@ -205,7 +206,9 @@
* When registering a service, {@link NsdManager#FAILURE_BAD_PARAMETERS} will be returned
* if {@code ttl} is smaller than 30 seconds.
*
- * Note: only number of seconds of {@code ttl} is used.
+ * Note: the value after the decimal point (in unit of seconds) will be discarded. For
+ * example, {@code 30} seconds will be used when {@code Duration.ofSeconds(30L, 50_000L)}
+ * is provided.
*
* @param ttl the maximum duration that the DNS resource records will be cached
*
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 f4cc2ac..d8cccb2 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -16,6 +16,8 @@
package android.net.nsd;
+import static com.android.net.module.util.HexDump.toHexString;
+
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -35,10 +37,12 @@
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;
import java.util.Set;
+import java.util.StringJoiner;
/**
* A class representing service information for network service discovery
@@ -66,11 +70,15 @@
private int mPort;
@Nullable
+ private byte[] mPublicKey;
+
+ @Nullable
private Network mNetwork;
private int mInterfaceIndex;
- // The timestamp that all resource records associated with this service are considered invalid.
+ // The timestamp that one or more resource records associated with this service are considered
+ // invalid.
@Nullable
private Instant mExpirationTime;
@@ -172,8 +180,18 @@
return new ArrayList<>(mHostAddresses);
}
- /** Set the host addresses */
+ /**
+ * Set the host addresses.
+ *
+ * <p>When registering hosts/services, there can only be one registration including address
+ * records for a given hostname.
+ *
+ * <p>For example, if a client registers a service with the hostname "MyHost" and the address
+ * records of 192.168.1.1 and 192.168.1.2, then other registrations for the hostname "MyHost"
+ * must not have any address record, otherwise there will be a conflict.
+ */
public void setHostAddresses(@NonNull List<InetAddress> addresses) {
+ // TODO: b/284905335 - Notify the client when there is a conflict.
mHostAddresses.clear();
mHostAddresses.addAll(addresses);
}
@@ -216,6 +234,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.
@@ -497,7 +549,9 @@
/**
* Sets the timestamp after when this service is expired.
*
- * Note: only number of seconds of {@code expirationTime} is used.
+ * Note: the value after the decimal point (in unit of seconds) will be discarded. For
+ * example, {@code 30} seconds will be used when {@code Duration.ofSeconds(30L, 50_000L)}
+ * is provided.
*
* @hide
*/
@@ -538,11 +592,50 @@
.append(", network: ").append(mNetwork)
.append(", expirationTime: ").append(mExpirationTime);
- byte[] txtRecord = getTxtRecord();
- sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
+ final StringJoiner txtJoiner =
+ new StringJoiner(", " /* delimiter */, "{" /* prefix */, "}" /* suffix */);
+
+ sb.append(", txtRecord: ");
+ for (int i = 0; i < mTxtRecord.size(); i++) {
+ txtJoiner.add(mTxtRecord.keyAt(i) + "=" + getPrintableTxtValue(mTxtRecord.valueAt(i)));
+ }
+ sb.append(txtJoiner.toString());
return sb.toString();
}
+ /**
+ * Returns printable string for {@code txtValue}.
+ *
+ * If {@code txtValue} contains non-printable ASCII characters, a HEX string with prefix "0x"
+ * will be returned. Otherwise, the ASCII string of {@code txtValue} is returned.
+ *
+ */
+ private static String getPrintableTxtValue(@Nullable byte[] txtValue) {
+ if (txtValue == null) {
+ return "(null)";
+ }
+
+ if (containsNonPrintableChars(txtValue)) {
+ return "0x" + toHexString(txtValue);
+ }
+
+ return new String(txtValue, StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Returns {@code true} if {@code txtValue} contains non-printable ASCII characters.
+ *
+ * The printable characters are in range of [32, 126].
+ */
+ private static boolean containsNonPrintableChars(byte[] txtValue) {
+ for (int i = 0; i < txtValue.length; i++) {
+ if (txtValue[i] < 32 || txtValue[i] > 126) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Implement the Parcelable interface */
public int describeContents() {
return 0;
@@ -577,6 +670,7 @@
}
dest.writeString(mHostname);
dest.writeLong(mExpirationTime != null ? mExpirationTime.getEpochSecond() : -1);
+ dest.writeByteArray(mPublicKey);
}
/** Implement the Parcelable interface */
@@ -609,6 +703,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 52f2c7c..deb1c5a 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -96,6 +96,7 @@
],
impl_only_static_libs: [
"net-utils-device-common-bpf",
+ "net-utils-device-common-struct-base",
],
libs: [
"androidx.annotation_annotation",
@@ -116,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
@@ -124,6 +124,7 @@
// Even if the library is included in "impl_only_static_libs" of defaults. This is still
// needed because java_library which doesn't understand "impl_only_static_libs".
"net-utils-device-common-bpf",
+ "net-utils-device-common-struct-base",
],
libs: [
// This cannot be in the defaults clause above because if it were, it would be used
@@ -145,7 +146,6 @@
],
impl_only_static_libs: [
"httpclient_impl",
- "http_client_logging",
],
}
diff --git a/framework/api/current.txt b/framework/api/current.txt
index ef8415c..7bc0cf3 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -337,6 +337,7 @@
field public static final int NET_CAPABILITY_MCX = 23; // 0x17
field public static final int NET_CAPABILITY_MMS = 0; // 0x0
field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21
+ field @FlaggedApi("com.android.net.flags.net_capability_not_bandwidth_constrained") public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37; // 0x25
field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14
field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
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/lint-baseline.xml b/framework/lint-baseline.xml
index 2c0b15f..dddabef 100644
--- a/framework/lint-baseline.xml
+++ b/framework/lint-baseline.xml
@@ -375,4 +375,279 @@
column="34"/>
</issue>
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_BACKGROUND` is a flagged API and should be inside an `if (Flags.basicBackgroundRestrictionsEnabled())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_BACKGROUND"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="115"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_METERED_ALLOW"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="137"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_METERED_DENY_USER,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="146"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_ADMIN` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_METERED_DENY_ADMIN"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="147"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_BACKGROUND` is a flagged API and should be inside an `if (Flags.basicBackgroundRestrictionsEnabled())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_BACKGROUND:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="133"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_METERED_ALLOW:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="143"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_METERED_DENY_USER:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="145"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_ADMIN` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_METERED_DENY_ADMIN:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="147"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `BLOCKED_REASON_APP_BACKGROUND` is a flagged API and should be inside an `if (Flags.basicBackgroundRestrictionsEnabled())` check (or annotate the surrounding method `getUidNetworkingBlockedReasons` with `@FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" blockedReasons |= BLOCKED_REASON_APP_BACKGROUND;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="293"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `BLOCKED_REASON_OEM_DENY` is a flagged API and should be inside an `if (Flags.blockedReasonOemDenyChains())` check (or annotate the surrounding method `getUidNetworkingBlockedReasons` with `@FlaggedApi(Flags.BLOCKED_REASON_OEM_DENY_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" blockedReasons |= BLOCKED_REASON_OEM_DENY;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="296"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `addUidToMeteredNetworkAllowList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6191"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `removeUidFromMeteredNetworkAllowList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DENY);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6214"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `addUidToMeteredNetworkDenyList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6243"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `removeUidFromMeteredNetworkDenyList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_ALLOW);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6273"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="767"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" defaultCapabilities |= (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="818"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="849"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getSubscriptionIds()` is a flagged API and should be inside an `if (Flags.requestRestrictedWifi())` check (or annotate the surrounding method `restrictCapabilitiesForTestNetwork` with `@FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI) to transfer requirement to caller`)"
+ errorLine1=" final Set<Integer> originalSubIds = getSubscriptionIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="1254"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TRANSPORT_SATELLITE` is a flagged API and should be inside an `if (Flags.supportTransportSatellite())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE) to transfer requirement to caller`)"
+ errorLine1=" public static final int MAX_TRANSPORT = TRANSPORT_SATELLITE;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="1383"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TRANSPORT_SATELLITE` is a flagged API and should be inside an `if (Flags.supportTransportSatellite())` check (or annotate the surrounding method `specifierAcceptableForMultipleTransports` with `@FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE) to transfer requirement to caller`)"
+ errorLine1=" == (1 << TRANSPORT_CELLULAR | 1 << TRANSPORT_SATELLITE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="1836"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_LOCAL_NETWORK` is a flagged API and should be inside an `if (Flags.netCapabilityLocalNetwork())` check (or annotate the surrounding method `capabilityNameOf` with `@FlaggedApi(Flags.FLAG_NET_CAPABILITY_LOCAL_NETWORK) to transfer requirement to caller`)"
+ errorLine1=" case NET_CAPABILITY_LOCAL_NETWORK: return "LOCAL_NETWORK";"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="2637"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `capabilityNameOf` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" case NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED: return "NOT_BANDWIDTH_CONSTRAINED";"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="2638"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TRANSPORT_SATELLITE` is a flagged API and should be inside an `if (Flags.supportTransportSatellite())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE) to transfer requirement to caller`)"
+ errorLine1=" TRANSPORT_SATELLITE"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java"
+ line="80"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+ line="291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getSubscriptionIds()` is a flagged API and should be inside an `if (Flags.requestRestrictedWifi())` check (or annotate the surrounding method `getSubscriptionIds` with `@FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI) to transfer requirement to caller`)"
+ errorLine1=" return networkCapabilities.getSubscriptionIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+ line="887"
+ column="16"/>
+ </issue>
+
</issues>
\ No newline at end of file
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/BpfNetMapsReader.java b/framework/src/android/net/BpfNetMapsReader.java
deleted file mode 100644
index ee422ab..0000000
--- a/framework/src/android/net/BpfNetMapsReader.java
+++ /dev/null
@@ -1,287 +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;
-
-import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
-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.PENALTY_BOX_MATCH;
-import static android.net.BpfNetMapsConstants.UID_OWNER_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.throwIfPreT;
-import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
-import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
-
-import android.annotation.NonNull;
-import android.annotation.RequiresApi;
-import android.os.Build;
-import android.os.ServiceSpecificException;
-import android.system.ErrnoException;
-import android.system.Os;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.build.SdkLevel;
-import com.android.net.module.util.BpfMap;
-import com.android.net.module.util.IBpfMap;
-import com.android.net.module.util.Struct.S32;
-import com.android.net.module.util.Struct.U32;
-import com.android.net.module.util.Struct.U8;
-
-/**
- * A helper class to *read* java BpfMaps.
- * @hide
- */
-@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
-public class BpfNetMapsReader {
- private static final String TAG = BpfNetMapsReader.class.getSimpleName();
-
- // Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the
- // BpfMap implementation.
-
- // Bpf map to store various networking configurations, the format of the value is different
- // for different keys. See BpfNetMapsConstants#*_CONFIGURATION_KEY for keys.
- private final IBpfMap<S32, U32> mConfigurationMap;
- // Bpf map to store per uid traffic control configurations.
- // See {@link UidOwnerValue} for more detail.
- private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
- private final IBpfMap<S32, U8> mDataSaverEnabledMap;
- private final Dependencies mDeps;
-
- // Bitmaps for calculating whether a given uid is blocked by firewall chains.
- private static final long sMaskDropIfSet;
- private static final long sMaskDropIfUnset;
-
- static {
- long maskDropIfSet = 0L;
- long maskDropIfUnset = 0L;
-
- for (int chain : BpfNetMapsConstants.ALLOW_CHAINS) {
- final long match = getMatchByFirewallChain(chain);
- maskDropIfUnset |= match;
- }
- for (int chain : BpfNetMapsConstants.DENY_CHAINS) {
- final long match = getMatchByFirewallChain(chain);
- maskDropIfSet |= match;
- }
- sMaskDropIfSet = maskDropIfSet;
- sMaskDropIfUnset = maskDropIfUnset;
- }
-
- private static class SingletonHolder {
- static final BpfNetMapsReader sInstance = new BpfNetMapsReader();
- }
-
- @NonNull
- public static BpfNetMapsReader getInstance() {
- return SingletonHolder.sInstance;
- }
-
- private BpfNetMapsReader() {
- this(new Dependencies());
- }
-
- // While the production code uses the singleton to optimize for performance and deal with
- // concurrent access, the test needs to use a non-static approach for dependency injection and
- // mocking virtual bpf maps.
- @VisibleForTesting
- public BpfNetMapsReader(@NonNull Dependencies deps) {
- if (!SdkLevel.isAtLeastT()) {
- throw new UnsupportedOperationException(
- BpfNetMapsReader.class.getSimpleName() + " is not supported below Android T");
- }
- mDeps = deps;
- mConfigurationMap = mDeps.getConfigurationMap();
- mUidOwnerMap = mDeps.getUidOwnerMap();
- mDataSaverEnabledMap = mDeps.getDataSaverEnabledMap();
- }
-
- /**
- * Dependencies of BpfNetMapReader, for injection in tests.
- */
- @VisibleForTesting
- public static class Dependencies {
- /** Get the configuration map. */
- public IBpfMap<S32, U32> getConfigurationMap() {
- try {
- return new BpfMap<>(CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDONLY,
- S32.class, U32.class);
- } catch (ErrnoException e) {
- throw new IllegalStateException("Cannot open configuration map", e);
- }
- }
-
- /** Get the uid owner map. */
- public IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
- try {
- return new BpfMap<>(UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDONLY,
- S32.class, UidOwnerValue.class);
- } catch (ErrnoException e) {
- throw new IllegalStateException("Cannot open uid owner map", e);
- }
- }
-
- /** Get the data saver enabled map. */
- public IBpfMap<S32, U8> getDataSaverEnabledMap() {
- try {
- return new BpfMap<>(DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDONLY, S32.class,
- U8.class);
- } catch (ErrnoException e) {
- throw new IllegalStateException("Cannot open data saver enabled map", e);
- }
- }
- }
-
- /**
- * Get the specified firewall chain's status.
- *
- * @param chain target chain
- * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
- * @throws UnsupportedOperationException if called on pre-T devices.
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public boolean isChainEnabled(final int chain) {
- return isChainEnabled(mConfigurationMap, chain);
- }
-
- /**
- * Get firewall rule of specified firewall chain on specified uid.
- *
- * @param chain target chain
- * @param uid target uid
- * @return either {@link ConnectivityManager#FIREWALL_RULE_ALLOW} or
- * {@link ConnectivityManager#FIREWALL_RULE_DENY}.
- * @throws UnsupportedOperationException if called on pre-T devices.
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public int getUidRule(final int chain, final int uid) {
- return getUidRule(mUidOwnerMap, chain, uid);
- }
-
- /**
- * Get the specified firewall chain's status.
- *
- * @param configurationMap target configurationMap
- * @param chain target chain
- * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
- * @throws UnsupportedOperationException if called on pre-T devices.
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public static boolean isChainEnabled(
- final IBpfMap<S32, U32> configurationMap, final int chain) {
- throwIfPreT("isChainEnabled is not available on pre-T devices");
-
- final long match = getMatchByFirewallChain(chain);
- try {
- final U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
- return (config.val & match) != 0;
- } catch (ErrnoException e) {
- throw new ServiceSpecificException(e.errno,
- "Unable to get firewall chain status: " + Os.strerror(e.errno));
- }
- }
-
- /**
- * Get firewall rule of specified firewall chain on specified uid.
- *
- * @param uidOwnerMap target uidOwnerMap.
- * @param chain target chain.
- * @param uid target uid.
- * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
- * @throws UnsupportedOperationException if called on pre-T devices.
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public static int getUidRule(final IBpfMap<S32, UidOwnerValue> uidOwnerMap,
- final int chain, final int uid) {
- throwIfPreT("getUidRule is not available on pre-T devices");
-
- final long match = getMatchByFirewallChain(chain);
- final boolean isAllowList = isFirewallAllowList(chain);
- try {
- final UidOwnerValue uidMatch = uidOwnerMap.getValue(new S32(uid));
- final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
- return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
- } catch (ErrnoException e) {
- throw new ServiceSpecificException(e.errno,
- "Unable to get uid rule status: " + Os.strerror(e.errno));
- }
- }
-
- /**
- * Return whether the network is blocked by firewall chains for the given uid.
- *
- * @param uid The target uid.
- * @param isNetworkMetered Whether the target network is metered.
- * @param isDataSaverEnabled Whether the data saver is enabled.
- *
- * @return True if the network is blocked. Otherwise, false.
- * @throws ServiceSpecificException if the read fails.
- *
- * @hide
- */
- public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
- boolean isDataSaverEnabled) {
- throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
-
- final long uidRuleConfig;
- final long uidMatch;
- try {
- uidRuleConfig = mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
- final UidOwnerValue value = mUidOwnerMap.getValue(new 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 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 isDataSaverEnabled;
- }
-
- /**
- * Get Data Saver enabled or disabled
- *
- * @return whether Data Saver is enabled or disabled.
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public boolean getDataSaverEnabled() {
- throwIfPreT("getDataSaverEnabled is not available on pre-T devices");
-
- try {
- return mDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val == DATA_SAVER_ENABLED;
- } catch (ErrnoException e) {
- throw new ServiceSpecificException(e.errno, "Unable to get data saver: "
- + Os.strerror(e.errno));
- }
- }
-}
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 0be30bb..282a11e 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -18,32 +18,69 @@
import static android.net.BpfNetMapsConstants.ALLOW_CHAINS;
import static android.net.BpfNetMapsConstants.BACKGROUND_MATCH;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
import static android.net.BpfNetMapsConstants.DENY_CHAINS;
import static android.net.BpfNetMapsConstants.DOZABLE_MATCH;
+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_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;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+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.os.UserHandle;
+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;
+import com.android.net.module.util.Struct.S32;
+import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.U8;
import java.util.StringJoiner;
@@ -55,7 +92,29 @@
// 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;
+ private static final long sMaskDropIfUnset;
+
+ static {
+ long maskDropIfSet = 0L;
+ long maskDropIfUnset = 0L;
+
+ for (int chain : BpfNetMapsConstants.ALLOW_CHAINS) {
+ final long match = getMatchByFirewallChain(chain);
+ maskDropIfUnset |= match;
+ }
+ for (int chain : BpfNetMapsConstants.DENY_CHAINS) {
+ final long match = getMatchByFirewallChain(chain);
+ maskDropIfSet |= match;
+ }
+ sMaskDropIfSet = maskDropIfSet;
+ sMaskDropIfUnset = maskDropIfUnset;
+ }
+
// Prevent this class from being accidental instantiated.
private BpfNetMapsUtils() {}
@@ -82,6 +141,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);
}
@@ -94,9 +159,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);
@@ -133,4 +198,178 @@
throw new UnsupportedOperationException(msg);
}
}
+
+ /**
+ * Get the specified firewall chain's status.
+ *
+ * @param configurationMap target configurationMap
+ * @param chain target chain
+ * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
+ * @throws UnsupportedOperationException if called on pre-T devices.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public static boolean isChainEnabled(
+ final IBpfMap<S32, U32> configurationMap, final int chain) {
+ throwIfPreT("isChainEnabled is not available on pre-T devices");
+
+ final long match = getMatchByFirewallChain(chain);
+ try {
+ final U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
+ return (config.val & match) != 0;
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno,
+ "Unable to get firewall chain status: " + Os.strerror(e.errno));
+ }
+ }
+
+ /**
+ * Get firewall rule of specified firewall chain on specified uid.
+ *
+ * @param uidOwnerMap target uidOwnerMap.
+ * @param chain target chain.
+ * @param uid target uid.
+ * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+ * @throws UnsupportedOperationException if called on pre-T devices.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public static int getUidRule(final IBpfMap<S32, UidOwnerValue> uidOwnerMap,
+ final int chain, final int uid) {
+ throwIfPreT("getUidRule is not available on pre-T devices");
+
+ final long match = getMatchByFirewallChain(chain);
+ final boolean isAllowList = isFirewallAllowList(chain);
+ try {
+ final UidOwnerValue uidMatch = uidOwnerMap.getValue(new S32(uid));
+ final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
+ return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno,
+ "Unable to get uid rule status: " + Os.strerror(e.errno));
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param uid The target uid.
+ * @param isNetworkMetered Whether the target network is metered.
+ *
+ * @return True if the network is blocked. Otherwise, false.
+ * @throws ServiceSpecificException if the read fails.
+ *
+ * @hide
+ */
+ public static boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
+ IBpfMap<S32, U32> configurationMap,
+ IBpfMap<S32, UidOwnerValue> uidOwnerMap,
+ IBpfMap<S32, U8> dataSaverEnabledMap
+ ) {
+ throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
+
+ // System uids are not blocked by firewall chains, see bpf_progs/netd.c
+ // TODO: b/348513058 - use UserHandle.isCore() once it is accessible
+ if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
+ return false;
+ }
+
+ 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;
+ }
+ }
+
+ /**
+ * Get Data Saver enabled or disabled
+ *
+ * Note that before V, the data saver status in bpf is written by ConnectivityService
+ * when receiving {@link ConnectivityManager#ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus,
+ * the status is not synchronized.
+ * On V+, the data saver status is set by platform code when enabling/disabling
+ * data saver, which is synchronized.
+ *
+ * @return whether Data Saver is enabled or disabled.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public static boolean getDataSaverEnabled(IBpfMap<S32, U8> dataSaverEnabledMap) {
+ throwIfPreT("getDataSaverEnabled is not available on pre-T devices");
+
+ try {
+ return dataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val == DATA_SAVER_ENABLED;
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno, "Unable to get data saver: "
+ + Os.strerror(e.errno));
+ }
+ }
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 915ec52..ffaf41f 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;
@@ -70,23 +71,29 @@
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.LruCache;
import android.util.Range;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import libcore.net.event.NetworkEventDispatcher;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -128,6 +135,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 +924,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 +987,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 +1107,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 +1173,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 +1214,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 +1237,27 @@
@GuardedBy("mTetheringEventCallbacks")
private TetheringManager mTetheringManager;
+ // Cache of the most recently used NetworkCallback classes (not instances) -> method flags.
+ // 100 is chosen kind arbitrarily as an unlikely number of different types of NetworkCallback
+ // overrides that a process may have, and should generally not be reached (for example, the
+ // system server services.jar has been observed with dexdump to have only 16 when this was
+ // added, and a very large system services app only had 18).
+ // If this number is exceeded, the code will still function correctly, but re-registering
+ // using a network callback class that was used before, but 100+ other classes have been used in
+ // the meantime, will be a bit slower (as slow as the first registration) because
+ // getDeclaredMethodsFlag must re-examine the callback class to determine what methods it
+ // overrides.
+ private static final LruCache<Class<? extends NetworkCallback>, Integer> sMethodFlagsCache =
+ new LruCache<>(100);
+
+ 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) {
@@ -3887,6 +4015,55 @@
*/
public static class NetworkCallback {
/**
+ * Bitmask of method flags with all flags set.
+ * @hide
+ */
+ public static final int DECLARED_METHODS_ALL = ~0;
+
+ /**
+ * Bitmask of method flags with no flag set.
+ * @hide
+ */
+ public static final int DECLARED_METHODS_NONE = 0;
+
+ // Tracks whether an instance was created via reflection without calling the constructor.
+ private final boolean mConstructorWasCalled;
+
+ /**
+ * Annotation for NetworkCallback methods to verify filtering is configured properly.
+ *
+ * This is only used in tests to ensure that tests fail when a new callback is added, or
+ * callbacks are modified, without updating
+ * {@link NetworkCallbackMethodsHolder#NETWORK_CB_METHODS} properly.
+ * @hide
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ @VisibleForTesting
+ public @interface FilteredCallback {
+ /**
+ * The NetworkCallback.METHOD_* ID of this method.
+ */
+ int methodId();
+
+ /**
+ * The ConnectivityManager.CALLBACK_* message that this method is directly called by.
+ *
+ * If this method is not called by any message, this should be
+ * {@link #CALLBACK_TRANSITIVE_CALLS_ONLY}.
+ */
+ int calledByCallbackId();
+
+ /**
+ * If this method may call other NetworkCallback methods, an array of methods it calls.
+ *
+ * Only direct calls (not transitive calls) should be included. The IDs must be
+ * NetworkCallback.METHOD_* IDs.
+ */
+ int[] mayCall() default {};
+ }
+
+ /**
* No flags associated with this callback.
* @hide
*/
@@ -3949,6 +4126,7 @@
throw new IllegalArgumentException("Invalid flags");
}
mFlags = flags;
+ mConstructorWasCalled = true;
}
/**
@@ -3966,7 +4144,9 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONPRECHECK, calledByCallbackId = CALLBACK_PRECHECK)
public void onPreCheck(@NonNull Network network) {}
+ private static final int METHOD_ONPRECHECK = 1;
/**
* Called when the framework connects and has declared a new network ready for use.
@@ -3981,6 +4161,11 @@
* @param blocked Whether access to the {@link Network} is blocked due to system policy.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_5ARGS,
+ calledByCallbackId = CALLBACK_AVAILABLE,
+ mayCall = { METHOD_ONAVAILABLE_4ARGS,
+ METHOD_ONLOCALNETWORKINFOCHANGED,
+ METHOD_ONBLOCKEDSTATUSCHANGED_INT })
public final void onAvailable(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
@@ -3993,6 +4178,7 @@
if (null != localInfo) onLocalNetworkInfoChanged(network, localInfo);
onBlockedStatusChanged(network, blocked);
}
+ private static final int METHOD_ONAVAILABLE_5ARGS = 2;
/**
* Legacy variant of onAvailable that takes a boolean blocked reason.
@@ -4005,6 +4191,13 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_4ARGS,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY,
+ mayCall = { METHOD_ONAVAILABLE_1ARG,
+ METHOD_ONNETWORKSUSPENDED,
+ METHOD_ONCAPABILITIESCHANGED,
+ METHOD_ONLINKPROPERTIESCHANGED
+ })
public void onAvailable(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
@@ -4018,6 +4211,7 @@
onLinkPropertiesChanged(network, linkProperties);
// No call to onBlockedStatusChanged here. That is done by the caller.
}
+ private static final int METHOD_ONAVAILABLE_4ARGS = 3;
/**
* Called when the framework connects and has declared a new network ready for use.
@@ -4048,7 +4242,10 @@
*
* @param network The {@link Network} of the satisfying network.
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_1ARG,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY)
public void onAvailable(@NonNull Network network) {}
+ private static final int METHOD_ONAVAILABLE_1ARG = 4;
/**
* Called when the network is about to be lost, typically because there are no outstanding
@@ -4067,7 +4264,9 @@
* connected for graceful handover; note that the network may still
* suffer a hard loss at any time.
*/
+ @FilteredCallback(methodId = METHOD_ONLOSING, calledByCallbackId = CALLBACK_LOSING)
public void onLosing(@NonNull Network network, int maxMsToLive) {}
+ private static final int METHOD_ONLOSING = 5;
/**
* Called when a network disconnects or otherwise no longer satisfies this request or
@@ -4088,7 +4287,9 @@
*
* @param network The {@link Network} lost.
*/
+ @FilteredCallback(methodId = METHOD_ONLOST, calledByCallbackId = CALLBACK_LOST)
public void onLost(@NonNull Network network) {}
+ private static final int METHOD_ONLOST = 6;
/**
* Called if no network is found within the timeout time specified in
@@ -4098,7 +4299,9 @@
* {@link NetworkRequest} will have already been removed and released, as if
* {@link #unregisterNetworkCallback(NetworkCallback)} had been called.
*/
+ @FilteredCallback(methodId = METHOD_ONUNAVAILABLE, calledByCallbackId = CALLBACK_UNAVAIL)
public void onUnavailable() {}
+ private static final int METHOD_ONUNAVAILABLE = 7;
/**
* Called when the network corresponding to this request changes capabilities but still
@@ -4115,8 +4318,11 @@
* @param networkCapabilities The new {@link NetworkCapabilities} for this
* network.
*/
+ @FilteredCallback(methodId = METHOD_ONCAPABILITIESCHANGED,
+ calledByCallbackId = CALLBACK_CAP_CHANGED)
public void onCapabilitiesChanged(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities) {}
+ private static final int METHOD_ONCAPABILITIESCHANGED = 8;
/**
* Called when the network corresponding to this request changes {@link LinkProperties}.
@@ -4131,8 +4337,11 @@
* @param network The {@link Network} whose link properties have changed.
* @param linkProperties The new {@link LinkProperties} for this network.
*/
+ @FilteredCallback(methodId = METHOD_ONLINKPROPERTIESCHANGED,
+ calledByCallbackId = CALLBACK_IP_CHANGED)
public void onLinkPropertiesChanged(@NonNull Network network,
@NonNull LinkProperties linkProperties) {}
+ private static final int METHOD_ONLINKPROPERTIESCHANGED = 9;
/**
* Called when there is a change in the {@link LocalNetworkInfo} for this network.
@@ -4144,8 +4353,11 @@
* @param localNetworkInfo the new {@link LocalNetworkInfo} for this network.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONLOCALNETWORKINFOCHANGED,
+ calledByCallbackId = CALLBACK_LOCAL_NETWORK_INFO_CHANGED)
public void onLocalNetworkInfoChanged(@NonNull Network network,
@NonNull LocalNetworkInfo localNetworkInfo) {}
+ private static final int METHOD_ONLOCALNETWORKINFOCHANGED = 10;
/**
* Called when the network the framework connected to for this request suspends data
@@ -4164,7 +4376,10 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONNETWORKSUSPENDED,
+ calledByCallbackId = CALLBACK_SUSPENDED)
public void onNetworkSuspended(@NonNull Network network) {}
+ private static final int METHOD_ONNETWORKSUSPENDED = 11;
/**
* Called when the network the framework connected to for this request
@@ -4178,7 +4393,9 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONNETWORKRESUMED, calledByCallbackId = CALLBACK_RESUMED)
public void onNetworkResumed(@NonNull Network network) {}
+ private static final int METHOD_ONNETWORKRESUMED = 12;
/**
* Called when access to the specified network is blocked or unblocked.
@@ -4191,7 +4408,10 @@
* @param network The {@link Network} whose blocked status has changed.
* @param blocked The blocked status of this {@link Network}.
*/
+ @FilteredCallback(methodId = METHOD_ONBLOCKEDSTATUSCHANGED_BOOL,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY)
public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
+ private static final int METHOD_ONBLOCKEDSTATUSCHANGED_BOOL = 13;
/**
* Called when access to the specified network is blocked or unblocked, or the reason for
@@ -4209,10 +4429,14 @@
* @param blocked The blocked status of this {@link Network}.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONBLOCKEDSTATUSCHANGED_INT,
+ calledByCallbackId = CALLBACK_BLK_CHANGED,
+ mayCall = { METHOD_ONBLOCKEDSTATUSCHANGED_BOOL })
@SystemApi(client = MODULE_LIBRARIES)
public void onBlockedStatusChanged(@NonNull Network network, @BlockedReason int blocked) {
onBlockedStatusChanged(network, blocked != 0);
}
+ private static final int METHOD_ONBLOCKEDSTATUSCHANGED_INT = 14;
private NetworkRequest networkRequest;
private final int mFlags;
@@ -4240,6 +4464,7 @@
}
}
+ private static final int CALLBACK_TRANSITIVE_CALLS_ONLY = 0;
/** @hide */
public static final int CALLBACK_PRECHECK = 1;
/** @hide */
@@ -4265,9 +4490,11 @@
/** @hide */
public static final int CALLBACK_LOCAL_NETWORK_INFO_CHANGED = 12;
+
/** @hide */
public static String getCallbackName(int whichCallback) {
switch (whichCallback) {
+ case CALLBACK_TRANSITIVE_CALLS_ONLY: return "CALLBACK_TRANSITIVE_CALLS_ONLY";
case CALLBACK_PRECHECK: return "CALLBACK_PRECHECK";
case CALLBACK_AVAILABLE: return "CALLBACK_AVAILABLE";
case CALLBACK_LOSING: return "CALLBACK_LOSING";
@@ -4285,6 +4512,68 @@
}
}
+ /** @hide */
+ @VisibleForTesting
+ public static class NetworkCallbackMethod {
+ @NonNull
+ public final String mName;
+ @NonNull
+ public final Class<?>[] mParameterTypes;
+ // Bitmask of CALLBACK_* that may transitively call this method.
+ public final int mCallbacksCallingThisMethod;
+
+ public NetworkCallbackMethod(@NonNull String name, @NonNull Class<?>[] parameterTypes,
+ int callbacksCallingThisMethod) {
+ mName = name;
+ mParameterTypes = parameterTypes;
+ mCallbacksCallingThisMethod = callbacksCallingThisMethod;
+ }
+ }
+
+ // Holder class for the list of NetworkCallbackMethod. This ensures the list is only created
+ // once on first usage, and not just on ConnectivityManager class initialization.
+ /** @hide */
+ @VisibleForTesting
+ public static class NetworkCallbackMethodsHolder {
+ public static final NetworkCallbackMethod[] NETWORK_CB_METHODS =
+ new NetworkCallbackMethod[] {
+ method("onPreCheck", 1 << CALLBACK_PRECHECK, Network.class),
+ // Note the final overload of onAvailable is not included, since it cannot
+ // match any overridden method.
+ method("onAvailable", 1 << CALLBACK_AVAILABLE, Network.class),
+ method("onAvailable", 1 << CALLBACK_AVAILABLE,
+ Network.class, NetworkCapabilities.class,
+ LinkProperties.class, boolean.class),
+ method("onLosing", 1 << CALLBACK_LOSING, Network.class, int.class),
+ method("onLost", 1 << CALLBACK_LOST, Network.class),
+ method("onUnavailable", 1 << CALLBACK_UNAVAIL),
+ method("onCapabilitiesChanged",
+ 1 << CALLBACK_CAP_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, NetworkCapabilities.class),
+ method("onLinkPropertiesChanged",
+ 1 << CALLBACK_IP_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, LinkProperties.class),
+ method("onLocalNetworkInfoChanged",
+ 1 << CALLBACK_LOCAL_NETWORK_INFO_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, LocalNetworkInfo.class),
+ method("onNetworkSuspended",
+ 1 << CALLBACK_SUSPENDED | 1 << CALLBACK_AVAILABLE, Network.class),
+ method("onNetworkResumed",
+ 1 << CALLBACK_RESUMED, Network.class),
+ method("onBlockedStatusChanged",
+ 1 << CALLBACK_BLK_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, boolean.class),
+ method("onBlockedStatusChanged",
+ 1 << CALLBACK_BLK_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, int.class),
+ };
+
+ private static NetworkCallbackMethod method(
+ String name, int callbacksCallingThisMethod, Class<?>... args) {
+ return new NetworkCallbackMethod(name, args, callbacksCallingThisMethod);
+ }
+ }
+
private static class CallbackHandler extends Handler {
private static final String TAG = "ConnectivityManager.CallbackHandler";
private static final boolean DBG = false;
@@ -4404,6 +4693,14 @@
if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) {
throw new IllegalArgumentException("null NetworkCapabilities");
}
+
+
+ final boolean useDeclaredMethods = isFeatureEnabled(
+ FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS);
+ // Set all bits if the feature is disabled
+ int declaredMethodsFlag = useDeclaredMethods
+ ? tryGetDeclaredMethodsFlag(callback)
+ : NetworkCallback.DECLARED_METHODS_ALL;
final NetworkRequest request;
final String callingPackageName = mContext.getOpPackageName();
try {
@@ -4420,11 +4717,12 @@
if (reqType == LISTEN) {
request = mService.listenForNetwork(
need, messenger, binder, callbackFlags, callingPackageName,
- getAttributionTag());
+ getAttributionTag(), declaredMethodsFlag);
} else {
request = mService.requestNetwork(
asUid, need, reqType.ordinal(), messenger, timeoutMs, binder,
- legacyType, callbackFlags, callingPackageName, getAttributionTag());
+ legacyType, callbackFlags, callingPackageName, getAttributionTag(),
+ declaredMethodsFlag);
}
if (request != null) {
sCallbacks.put(request, callback);
@@ -4439,6 +4737,122 @@
return request;
}
+ private int tryGetDeclaredMethodsFlag(@NonNull NetworkCallback cb) {
+ if (!cb.mConstructorWasCalled) {
+ // Do not use the optimization if the callback was created via reflection or mocking,
+ // as for example with dexmaker-mockito-inline methods will be instrumented without
+ // using subclasses. This does not catch all cases as it is still possible to call the
+ // constructor when creating mocks, but by default constructors are not called in that
+ // case.
+ return NetworkCallback.DECLARED_METHODS_ALL;
+ }
+ try {
+ return getDeclaredMethodsFlag(cb.getClass());
+ } catch (LinkageError e) {
+ // This may happen if some methods reference inaccessible classes in their arguments
+ // (for example b/261807130).
+ Log.w(TAG, "Could not get methods from NetworkCallback class", e);
+ // Fall through
+ } catch (Throwable e) {
+ // Log.wtf would be best but this is in app process, so the TerribleFailureHandler may
+ // have unknown effects, possibly crashing the app (default behavior on eng builds or
+ // if the WTF_IS_FATAL setting is set).
+ Log.e(TAG, "Unexpected error while getting methods from NetworkCallback class", e);
+ // Fall through
+ }
+ return NetworkCallback.DECLARED_METHODS_ALL;
+ }
+
+ private static int getDeclaredMethodsFlag(@NonNull Class<? extends NetworkCallback> clazz) {
+ final Integer cachedFlags = sMethodFlagsCache.get(clazz);
+ // As this is not synchronized, it is possible that this method will calculate the
+ // flags for a given class multiple times, but that is fine. LruCache itself is thread-safe.
+ if (cachedFlags != null) {
+ return cachedFlags;
+ }
+
+ int flag = 0;
+ // This uses getMethods instead of getDeclaredMethods, to make sure that if A overrides B
+ // that overrides NetworkCallback, A.getMethods also returns methods declared by B.
+ for (Method classMethod : clazz.getMethods()) {
+ final Class<?> declaringClass = classMethod.getDeclaringClass();
+ if (declaringClass == NetworkCallback.class) {
+ // The callback is as defined by NetworkCallback and not overridden
+ continue;
+ }
+ if (declaringClass == Object.class) {
+ // Optimization: no need to try to match callbacks for methods declared by Object
+ continue;
+ }
+ flag |= getCallbackIdsCallingThisMethod(classMethod);
+ }
+
+ if (flag == 0) {
+ // dexmaker-mockito-inline (InlineDexmakerMockMaker), for example for mockito-extended,
+ // modifies bytecode of classes in-place to add hooks instead of creating subclasses,
+ // which would not be detected. When no method is found, fall back to enabling callbacks
+ // for all methods.
+ // This will not catch the case where both NetworkCallback bytecode is modified and a
+ // subclass of NetworkCallback that has some overridden methods are used. But this kind
+ // of bytecode injection is only possible in debuggable processes, with a JVMTI debug
+ // agent attached, so it should not cause real issues.
+ // There may be legitimate cases where an empty callback is filed with no method
+ // overridden, for example requestNetwork(requestForCell, new NetworkCallback()) which
+ // would ensure that one cell network stays up. But there is no way to differentiate
+ // such NetworkCallbacks from a mock that called the constructor, so this code will
+ // register the callback with DECLARED_METHODS_ALL and turn off the optimization in that
+ // case. Apps are not expected to do this often anyway since the usefulness is very
+ // limited.
+ flag = NetworkCallback.DECLARED_METHODS_ALL;
+ }
+ sMethodFlagsCache.put(clazz, flag);
+ return flag;
+ }
+
+ /**
+ * Find out which of the base methods in NetworkCallback will call this method.
+ *
+ * For example, in the case of onLinkPropertiesChanged, this will be
+ * (1 << CALLBACK_IP_CHANGED) | (1 << CALLBACK_AVAILABLE).
+ */
+ private static int getCallbackIdsCallingThisMethod(@NonNull Method method) {
+ for (NetworkCallbackMethod baseMethod : NetworkCallbackMethodsHolder.NETWORK_CB_METHODS) {
+ if (!baseMethod.mName.equals(method.getName())) {
+ continue;
+ }
+ Class<?>[] methodParams = method.getParameterTypes();
+
+ // As per JLS 8.4.8.1., a method m1 must have a subsignature of method m2 to override
+ // it. And as per JLS 8.4.2, this means the erasure of the signature of m2 must be the
+ // same as the signature of m1. Since type erasure is done at compile time, with
+ // reflection the erased types are already observed, so the (erased) parameter types
+ // must be equal.
+ // So for example a method that is identical to a NetworkCallback method, except with
+ // one parameter being a subclass of the parameter in the original method, will never
+ // be called since it is not an override (the erasure of the arguments are not the same)
+ // Therefore, the method is an override only if methodParams is exactly equal to
+ // the base method's parameter types.
+ if (Arrays.equals(baseMethod.mParameterTypes, methodParams)) {
+ return baseMethod.mCallbacksCallingThisMethod;
+ }
+ }
+ return 0;
+ }
+
+ 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 +6479,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 +6502,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 +6512,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 +6531,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 +6542,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 +6561,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 +6619,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.
@@ -6284,15 +6716,16 @@
@RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) {
if (!SdkLevel.isAtLeastU()) {
- Log.wtf(TAG, "isUidNetworkingBlocked is not supported on pre-U devices");
+ throw new IllegalStateException(
+ "isUidNetworkingBlocked is not supported on pre-U devices");
}
- final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
+ final NetworkStackBpfNetMaps reader = NetworkStackBpfNetMaps.getInstance();
// Note that before V, the data saver status in bpf is written by ConnectivityService
// when receiving {@link #ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus,
// the status is not synchronized.
// On V+, the data saver status is set by platform code when enabling/disabling
// data saver, which is synchronized.
- return reader.isUidNetworkingBlocked(uid, isNetworkMetered, reader.getDataSaverEnabled());
+ return reader.isUidNetworkingBlocked(uid, isNetworkMetered);
}
/** @hide */
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index d3a02b9..988cc92 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -153,7 +153,8 @@
NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType,
in Messenger messenger, int timeoutSec, in IBinder binder, int legacy,
- int callbackFlags, String callingPackageName, String callingAttributionTag);
+ int callbackFlags, String callingPackageName, String callingAttributionTag,
+ int declaredMethodsFlag);
NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation, String callingPackageName, String callingAttributionTag);
@@ -162,7 +163,7 @@
NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, in IBinder binder, int callbackFlags, String callingPackageName,
- String callingAttributionTag);
+ String callingAttributionTag, int declaredMethodsFlag);
void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation, String callingPackageName,
@@ -242,10 +243,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 +260,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 84a0d29..6a14bde 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -36,9 +36,11 @@
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;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BitUtils;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkCapabilitiesUtils;
@@ -134,6 +136,8 @@
"com.android.net.flags.request_restricted_wifi";
static final String SUPPORT_TRANSPORT_SATELLITE =
"com.android.net.flags.support_transport_satellite";
+ static final String NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED =
+ "com.android.net.flags.net_capability_not_bandwidth_constrained";
}
/**
@@ -458,6 +462,7 @@
NET_CAPABILITY_PRIORITIZE_LATENCY,
NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
NET_CAPABILITY_LOCAL_NETWORK,
+ NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED,
})
public @interface NetCapability { }
@@ -740,7 +745,26 @@
@FlaggedApi(Flags.FLAG_NET_CAPABILITY_LOCAL_NETWORK)
public static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_LOCAL_NETWORK;
+ /**
+ * Indicates that this is not a bandwidth-constrained network.
+ *
+ * Starting from {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this capability is by default
+ * set in {@link NetworkRequest}s and true for most networks.
+ *
+ * If a network lacks this capability, it is bandwidth-constrained. Bandwidth constrained
+ * networks cannot support high-bandwidth data transfers and applications that request and use
+ * them must ensure that they limit bandwidth usage to below the values returned by
+ * {@link #getLinkDownstreamBandwidthKbps()} and {@link #getLinkUpstreamBandwidthKbps()} and
+ * limit the frequency of their network usage. If applications perform high-bandwidth data
+ * transfers on constrained networks or perform network access too frequently, the system may
+ * block the app's access to the network. The system may take other measures to reduce network
+ * usage on constrained networks, such as disabling network access to apps that are not in the
+ * foreground.
+ */
+ @FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37;
+
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
// Set all bits up to the MAX_NET_CAPABILITY-th bit
private static final long ALL_VALID_CAPABILITIES = (2L << MAX_NET_CAPABILITY) - 1;
@@ -749,22 +773,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 +808,17 @@
/**
* 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;
+ static {
+ long defaultCapabilities =
+ (1L << NET_CAPABILITY_NOT_RESTRICTED)
+ | (1L << NET_CAPABILITY_TRUSTED)
+ | (1L << NET_CAPABILITY_NOT_VPN);
+ if (SdkLevel.isAtLeastV()) {
+ defaultCapabilities |= (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+ }
+ DEFAULT_CAPABILITIES = defaultCapabilities;
+ }
/**
* Capabilities that are managed by ConnectivityService.
@@ -795,11 +826,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 +838,16 @@
* 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) |
+ (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+
/**
* Extra allowed capabilities for test networks that do not have TRANSPORT_CELLULAR. Test
@@ -824,7 +855,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 +876,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 +900,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 +917,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 +935,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 +1220,7 @@
* @hide
*/
public void maybeMarkCapabilitiesRestricted() {
- if (NetworkCapabilitiesUtils.inferRestrictedCapability(this)) {
+ if (NetworkCapabilitiesUtils.inferRestrictedCapability(mNetworkCapabilities)) {
removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
}
}
@@ -1368,12 +1414,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.
@@ -1775,8 +1820,7 @@
// use the same specifier, TelephonyNetworkSpecifier.
&& mTransportTypes != (1L << TRANSPORT_TEST)
&& Long.bitCount(mTransportTypes & ~(1L << TRANSPORT_TEST)) != 1
- && (mTransportTypes & ~(1L << TRANSPORT_TEST))
- != (1 << TRANSPORT_CELLULAR | 1 << TRANSPORT_SATELLITE)) {
+ && !specifierAcceptableForMultipleTransports(mTransportTypes)) {
throw new IllegalStateException("Must have a single non-test transport specified to "
+ "use setNetworkSpecifier");
}
@@ -1786,6 +1830,12 @@
return this;
}
+ private boolean specifierAcceptableForMultipleTransports(long transportTypes) {
+ return (transportTypes & ~(1L << TRANSPORT_TEST))
+ // Cellular and satellite use the same NetworkSpecifier.
+ == (1 << TRANSPORT_CELLULAR | 1 << TRANSPORT_SATELLITE);
+ }
+
/**
* Sets the optional transport specific information.
*
@@ -2585,6 +2635,7 @@
case NET_CAPABILITY_PRIORITIZE_LATENCY: return "PRIORITIZE_LATENCY";
case NET_CAPABILITY_PRIORITIZE_BANDWIDTH: return "PRIORITIZE_BANDWIDTH";
case NET_CAPABILITY_LOCAL_NETWORK: return "LOCAL_NETWORK";
+ case NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED: return "NOT_BANDWIDTH_CONSTRAINED";
default: return Integer.toString(capability);
}
}
@@ -2628,12 +2679,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/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index 4de02ac..502ac6f 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -20,7 +20,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -41,8 +41,6 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
-// TODO : replace with android.net.flags.Flags when aconfig is supported on udc-mainline-prod
-// import android.net.NetworkCapabilities.Flags;
import android.net.NetworkCapabilities.NetCapability;
import android.net.NetworkCapabilities.Transport;
import android.os.Build;
@@ -289,19 +287,8 @@
NET_CAPABILITY_PARTIAL_CONNECTIVITY,
NET_CAPABILITY_TEMPORARILY_NOT_METERED,
NET_CAPABILITY_TRUSTED,
- NET_CAPABILITY_VALIDATED);
-
- /**
- * Capabilities that are forbidden by default.
- * Forbidden capabilities only make sense in NetworkRequest, not for network agents.
- * Therefore these capabilities are only in NetworkRequest.
- */
- private static final int[] DEFAULT_FORBIDDEN_CAPABILITIES = new int[] {
- // TODO(b/313030307): this should contain NET_CAPABILITY_LOCAL_NETWORK.
- // We cannot currently add it because doing so would crash if the module rolls back,
- // because JobScheduler persists NetworkRequests to disk, and existing production code
- // does not consider LOCAL_NETWORK to be a valid capability.
- };
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
private final NetworkCapabilities mNetworkCapabilities;
@@ -318,16 +305,6 @@
// it for apps that do not have the NETWORK_SETTINGS permission.
mNetworkCapabilities = new NetworkCapabilities();
mNetworkCapabilities.setSingleUid(Process.myUid());
- // Default forbidden capabilities are foremost meant to help with backward
- // compatibility. When adding new types of network identified by a capability that
- // might confuse older apps, a default forbidden capability will have apps not see
- // these networks unless they explicitly ask for it.
- // If the app called clearCapabilities() it will see everything, but then it
- // can be argued that it's fair to send them too, since it asked for everything
- // explicitly.
- for (final int forbiddenCap : DEFAULT_FORBIDDEN_CAPABILITIES) {
- mNetworkCapabilities.addForbiddenCapability(forbiddenCap);
- }
}
/**
diff --git a/framework/src/android/net/NetworkStackBpfNetMaps.java b/framework/src/android/net/NetworkStackBpfNetMaps.java
new file mode 100644
index 0000000..b7c4e34
--- /dev/null
+++ b/framework/src/android/net/NetworkStackBpfNetMaps.java
@@ -0,0 +1,187 @@
+/*
+ * 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;
+
+import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH;
+import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresApi;
+import android.os.Build;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct.S32;
+import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.U8;
+
+/**
+ * A helper class to *read* java BpfMaps for network stack.
+ * BpfMap operations that are not used from network stack should be in
+ * {@link com.android.server.BpfNetMaps}
+ * @hide
+ */
+// NetworkStack can not use this before U due to b/326143935
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class NetworkStackBpfNetMaps {
+ private static final String TAG = NetworkStackBpfNetMaps.class.getSimpleName();
+
+ // Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the
+ // BpfMap implementation.
+
+ // Bpf map to store various networking configurations, the format of the value is different
+ // for different keys. See BpfNetMapsConstants#*_CONFIGURATION_KEY for keys.
+ private final IBpfMap<S32, U32> mConfigurationMap;
+ // Bpf map to store per uid traffic control configurations.
+ // See {@link UidOwnerValue} for more detail.
+ private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
+ private final IBpfMap<S32, U8> mDataSaverEnabledMap;
+ private final Dependencies mDeps;
+
+ private static class SingletonHolder {
+ static final NetworkStackBpfNetMaps sInstance = new NetworkStackBpfNetMaps();
+ }
+
+ @NonNull
+ public static NetworkStackBpfNetMaps getInstance() {
+ return SingletonHolder.sInstance;
+ }
+
+ private NetworkStackBpfNetMaps() {
+ this(new Dependencies());
+ }
+
+ // While the production code uses the singleton to optimize for performance and deal with
+ // concurrent access, the test needs to use a non-static approach for dependency injection and
+ // mocking virtual bpf maps.
+ @VisibleForTesting
+ public NetworkStackBpfNetMaps(@NonNull Dependencies deps) {
+ if (!SdkLevel.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ NetworkStackBpfNetMaps.class.getSimpleName()
+ + " is not supported below Android T");
+ }
+ mDeps = deps;
+ mConfigurationMap = mDeps.getConfigurationMap();
+ mUidOwnerMap = mDeps.getUidOwnerMap();
+ mDataSaverEnabledMap = mDeps.getDataSaverEnabledMap();
+ }
+
+ /**
+ * Dependencies of BpfNetMapReader, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /** Get the configuration map. */
+ public IBpfMap<S32, U32> getConfigurationMap() {
+ try {
+ return new BpfMap<>(CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDONLY,
+ S32.class, U32.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open configuration map", e);
+ }
+ }
+
+ /** Get the uid owner map. */
+ public IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
+ try {
+ return new BpfMap<>(UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDONLY,
+ S32.class, UidOwnerValue.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open uid owner map", e);
+ }
+ }
+
+ /** Get the data saver enabled map. */
+ public IBpfMap<S32, U8> getDataSaverEnabledMap() {
+ try {
+ return new BpfMap<>(DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDONLY, S32.class,
+ U8.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open data saver enabled map", e);
+ }
+ }
+ }
+
+ /**
+ * Get the specified firewall chain's status.
+ *
+ * @param chain target chain
+ * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
+ * @throws UnsupportedOperationException if called on pre-T devices.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public boolean isChainEnabled(final int chain) {
+ return BpfNetMapsUtils.isChainEnabled(mConfigurationMap, chain);
+ }
+
+ /**
+ * Get firewall rule of specified firewall chain on specified uid.
+ *
+ * @param chain target chain
+ * @param uid target uid
+ * @return either {@link ConnectivityManager#FIREWALL_RULE_ALLOW} or
+ * {@link ConnectivityManager#FIREWALL_RULE_DENY}.
+ * @throws UnsupportedOperationException if called on pre-T devices.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public int getUidRule(final int chain, final int uid) {
+ return BpfNetMapsUtils.getUidRule(mUidOwnerMap, chain, uid);
+ }
+
+ /**
+ * Return whether the network is blocked by firewall chains for the given uid.
+ *
+ * Note that {@link #getDataSaverEnabled()} has a latency before V.
+ *
+ * @param uid The target uid.
+ * @param isNetworkMetered Whether the target network is metered.
+ *
+ * @return True if the network is blocked. Otherwise, false.
+ * @throws ServiceSpecificException if the read fails.
+ *
+ * @hide
+ */
+ public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered) {
+ return BpfNetMapsUtils.isUidNetworkingBlocked(uid, isNetworkMetered,
+ mConfigurationMap, mUidOwnerMap, mDataSaverEnabledMap);
+ }
+
+ /**
+ * Get Data Saver enabled or disabled
+ *
+ * Note that before V, the data saver status in bpf is written by ConnectivityService
+ * when receiving {@link ConnectivityManager#ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus,
+ * the status is not synchronized.
+ * On V+, the data saver status is set by platform code when enabling/disabling
+ * data saver, which is synchronized.
+ *
+ * @return whether Data Saver is enabled or disabled.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public boolean getDataSaverEnabled() {
+ return BpfNetMapsUtils.getDataSaverEnabled(mDataSaverEnabledMap);
+ }
+}
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index fae2499..f92cdbb 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,22 @@
&& apfPacketFormat == other.apfPacketFormat;
}
+ @Override
+ public int hashCode() {
+ // hashCode it is not implemented in R. Therefore it would be dangerous for
+ // NetworkStack to depend on it.
+ 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 dfe5867..51df8ab 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -84,6 +84,57 @@
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long ENABLE_PLATFORM_MDNS_BACKEND = 270306772L;
+
+ /**
+ * Apps targeting Android V or higher receive network callbacks from local networks as default
+ *
+ * Apps targeting lower than {@link android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM} need
+ * to add {@link android.net.NetworkCapabilities#NET_CAPABILITY_LOCAL_NETWORK} to the
+ * {@link android.net.NetworkCapabilities} of the {@link android.net.NetworkRequest} to receive
+ * {@link android.net.ConnectivityManager.NetworkCallback} from local networks.
+ *
+ * @hide
+ */
+ @ChangeId
+ @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/framework/Android.bp b/nearby/framework/Android.bp
index 4be102c..41a28a0 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -58,4 +58,7 @@
visibility: [
"//packages/modules/Connectivity/nearby/tests:__subpackages__",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/nearby/framework/lint-baseline.xml b/nearby/framework/lint-baseline.xml
new file mode 100644
index 0000000..e1081ee
--- /dev/null
+++ b/nearby/framework/lint-baseline.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="529"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="573"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="605"
+ column="17"/>
+ </issue>
+
+</issues>
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..1e36676 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 public 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/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java
index 671b5c5..32286e1 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -33,6 +34,8 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import com.android.modules.utils.build.SdkLevel;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -68,6 +71,8 @@
@Before
public void setup() {
+ // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+ assumeTrue(SdkLevel.isAtLeastV());
MockitoAnnotations.initMocks(this);
mBluetoothFinderManager = new BluetoothFinderManagerSpy();
}
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index b71890e..908bb13 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -18,6 +18,18 @@
default_team: "trendy_team_fwk_core_networking",
}
+install_symlink {
+ name: "mainline_tethering_platform_components",
+
+ symlink_target: "/apex/com.android.tethering/bin/ethtool",
+ // installed_location is relative to /system because that's the default partition for soong
+ // modules, unless we add something like `system_ext_specific: true` like in hwservicemanager.
+ installed_location: "bin/ethtool",
+
+ init_rc: ["netbpfload.rc"],
+ required: ["bpfloader"],
+}
+
cc_binary {
name: "netbpfload",
@@ -49,9 +61,7 @@
// module "netbpfload" variant "android_x86_apex30": should support
// min_sdk_version(30) for "com.android.tethering": newer SDK(34).
min_sdk_version: "30",
-
- init_rc: ["netbpfload.rc"],
- required: ["bpfloader"],
+ installable: false,
}
// Versioned netbpfload init rc: init system will process it only on api T/33+ devices
@@ -60,8 +70,15 @@
// For details of versioned rc files see:
// https://android.googlesource.com/platform/system/core/+/HEAD/init/README.md#versioned-rc-files-within-apexs
prebuilt_etc {
- name: "netbpfload.mainline.rc",
- src: "netbpfload.mainline.rc",
+ name: "netbpfload.33rc",
+ src: "netbpfload.33rc",
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 ed7d048..0d4a5c4 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -51,23 +51,24 @@
#include "bpf/BpfUtils.h"
#include "loader.h"
-using android::base::EndsWith;
-using android::bpf::domain;
-using std::string;
+namespace android {
+namespace bpf {
-bool exists(const char* const path) {
+using base::StartsWith;
+using base::EndsWith;
+using std::string;
+using std::vector;
+
+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 +98,7 @@
},
};
-int loadAllElfObjects(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 +112,12 @@
progPath += s;
bool critical;
- int ret = android::bpf::loadProg(progPath.c_str(), &critical, 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 +125,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 +148,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));
@@ -171,10 +172,8 @@
#define APEX_MOUNT_POINT "/apex/com.android.tethering"
const char * const platformBpfLoader = "/system/bin/bpfloader";
-const char * const platformNetBpfLoad = "/system/bin/netbpfload";
-const char * const apexNetBpfLoad = APEX_MOUNT_POINT "/bin/netbpfload";
-int logTetheringApexVersion(void) {
+static int logTetheringApexVersion(void) {
char * found_blockdev = NULL;
FILE * f = NULL;
char buf[4096];
@@ -200,7 +199,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; }
@@ -226,46 +225,98 @@
return 0;
}
-int main(int argc, char** argv, char * const envp[]) {
- (void)argc;
- android::base::InitLogging(argv, &android::base::KernelLogger);
+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;
+}
- ALOGI("NetBpfLoad '%s' starting...", argv[0]);
+static bool isTV() {
+ if (hasGSM()) return false; // TVs don't do GSM
- // true iff we are running from the module
- const bool is_mainline = !strcmp(argv[0], apexNetBpfLoad);
+ 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;
+}
- // true iff we are running from the platform
- const bool is_platform = !strcmp(argv[0], platformNetBpfLoad);
+static bool isWear() {
+ static string wearSdkStr = base::GetProperty("ro.cw_build.wear_sdk.version", "");
+ static int wearSdkInt = base::GetIntProperty("ro.cw_build.wear_sdk.version", 0);
+ static string buildChars = base::GetProperty("ro.build.characteristics", "");
+ static vector<string> v = base::Tokenize(buildChars, ",");
+ static bool watch = (std::find(v.begin(), v.end(), "watch") != v.end());
+ static bool wear = (wearSdkInt > 0) || watch;
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("isWear(ro.cw_build.wear_sdk.version=%d[%s] ro.build.characteristics='%s'): %s",
+ wearSdkInt, wearSdkStr.c_str(), buildChars.c_str(), wear ? "true" : "false");
+ }
+ return wear;
+}
- 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__);
- const bool isAtLeastV = (device_api_level >= __ANDROID_API_V__);
+static int doLoad(char** argv, char * const envp[]) {
+ const bool runningAsRoot = !getuid(); // true iff U QPR3 or V+
+
+ // Any released device will have codename REL instead of a 'real' codename.
+ // For safety: default to 'REL' so we default to unreleased=false on failure.
+ const bool unreleased = (base::GetProperty("ro.build.version.codename", "REL") != "REL");
+
+ // goog/main device_api_level is bumped *way* before aosp/main api level
+ // (the latter only gets bumped during the push of goog/main to aosp/main)
+ //
+ // Since we develop in AOSP, we want it to behave as if it was bumped too.
+ //
+ // Note that AOSP doesn't really have a good api level (for example during
+ // early V dev cycle, it would have *all* of T, some but not all of U, and some V).
+ // One could argue that for our purposes AOSP api level should be infinite or 10000.
+ //
+ // This could also cause api to be increased in goog/main or other branches,
+ // but I can't imagine a case where this would be a problem: the problem
+ // is rather a too low api level, rather than some ill defined high value.
+ // For example as I write this aosp is 34/U, and goog is 35/V,
+ // we want to treat both goog & aosp as 35/V, but it's harmless if we
+ // treat goog as 36 because that value isn't yet defined to mean anything,
+ // and we thus never compare against it.
+ //
+ // Also note that 'android_get_device_api_level()' is what the
+ // //system/core/init/apex_init_util.cpp
+ // apex init .XXrc parsing code uses for XX filtering.
+ //
+ // That code has a hack to bump <35 to 35 (to force aosp/main to parse .35rc),
+ // but could (should?) perhaps be adjusted to match this.
+ const int effective_api_level = android_get_device_api_level() + (int)unreleased;
+ const bool isAtLeastT = (effective_api_level >= __ANDROID_API_T__);
+ const bool isAtLeastU = (effective_api_level >= __ANDROID_API_U__);
+ const bool isAtLeastV = (effective_api_level >= __ANDROID_API_V__);
// last in U QPR2 beta1
const bool has_platform_bpfloader_rc = exists("/system/etc/init/bpfloader.rc");
// 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 platform:%d mainline:%d rc:%d%d",
- android_get_application_target_sdk_version(), device_api_level,
- android::bpf::kernelVersion(), is_platform, is_mainline,
+ // Version of Network BpfLoader depends on the Android OS version
+ unsigned int bpfloader_ver = 42u; // [42] BPFLOADER_MAINLINE_VERSION
+ if (isAtLeastT) ++bpfloader_ver; // [43] BPFLOADER_MAINLINE_T_VERSION
+ if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
+ if (runningAsRoot) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_U_QPR3_VERSION
+ if (isAtLeastV) ++bpfloader_ver; // [46] BPFLOADER_MAINLINE_V_VERSION
+
+ ALOGI("NetBpfLoad v0.%u (%s) api:%d/%d kver:%07x (%s) uid:%d rc:%d%d",
+ bpfloader_ver, argv[0], android_get_device_api_level(), effective_api_level,
+ kernelVersion(), describeArch(), getuid(),
has_platform_bpfloader_rc, has_platform_netbpfload_rc);
- if (!is_platform && !is_mainline) {
- ALOGE("Unable to determine if we're platform or mainline netbpfload.");
- return 1;
- }
-
- if (is_platform) {
- ALOGI("Executing apex netbpfload...");
- const char * args[] = { apexNetBpfLoad, NULL, };
- execve(args[0], (char**)args, envp);
- ALOGE("exec '%s' fail: %d[%s]", apexNetBpfLoad, errno, strerror(errno));
- return 1;
- }
-
if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
ALOGE("Unable to find platform's bpfloader & netbpfload init scripts.");
return 1;
@@ -278,37 +329,72 @@
logTetheringApexVersion();
- if (is_mainline && has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
- // Tethering apex shipped initrc file causes us to reach here
- // but we're not ready to correctly handle anything before U QPR2
- // in which the 'bpfloader' vs 'netbpfload' split happened
- const char * args[] = { platformBpfLoader, NULL, };
- execve(args[0], (char**)args, envp);
- ALOGE("exec '%s' fail: %d[%s]", platformBpfLoader, errno, strerror(errno));
+ if (!isAtLeastT) {
+ ALOGE("Impossible - not reachable on Android <T.");
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) {
+ 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
@@ -324,28 +410,42 @@
* 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;
+ // Stuff won't work reliably, but exempt TVs & Arm Wear devices
+ if (!isTV() && !(isWear() && isArm())) 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 (isAtLeastU) {
+ if (runningAsRoot) {
+ // Note: writing this proc file requires being root (always the case on V+)
+
// 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) {
+ // Note: writing these proc files requires CAP_NET_ADMIN
+ // and sepolicy which is only present on U+,
+ // on Android T and earlier versions they're written from the 'load_bpf_programs'
+ // trigger (ie. by init itself) instead.
// Enable the eBPF JIT -- but do note that on 64-bit kernels it is likely
// already force enabled by the kernel config option BPF_JIT_ALWAYS_ON.
@@ -378,7 +478,7 @@
// Load all ELF objects, create programs and maps, and pin them
for (const auto& location : locations) {
- if (loadAllElfObjects(location) != 0) {
+ if (loadAllElfObjects(bpfloader_ver, location) != 0) {
ALOGE("=== CRITICAL FAILURE LOADING BPF PROGRAMS FROM %s ===", location.dir);
ALOGE("If this triggers reliably, you're probably missing kernel options or patches.");
ALOGE("If this triggers randomly, you might be hitting some memory allocation "
@@ -391,17 +491,49 @@
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;
}
+ // leave a flag that we're done
+ if (createSysFsBpfSubDir("netd_shared/mainline_done")) return 1;
+
+ // platform bpfloader will only succeed when run as root
+ if (!runningAsRoot) {
+ // unreachable on U QPR3+ which always runs netbpfload as root
+
+ ALOGI("mainline done, no need to transfer control to platform bpf loader.");
+ return 0;
+ }
+
+ // unreachable before U QPR3
ALOGI("done, transferring control to platform bpfloader.");
+ // platform BpfLoader *needs* to run as root
const char * args[] = { platformBpfLoader, NULL, };
execve(args[0], (char**)args, envp);
ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
return 1;
}
+
+} // 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 c534b2c..5141095 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>
@@ -31,24 +31,11 @@
#include <sys/wait.h>
#include <unistd.h>
-// This is BpfLoader v0.41
-// WARNING: If you ever hit cherrypick conflicts here you're doing it wrong:
-// You are NOT allowed to cherrypick bpfloader related patches out of order.
-// (indeed: cherrypicking is probably a bad idea and you should merge instead)
-// Mainline supports ONLY the published versions of the bpfloader for each Android release.
-#define BPFLOADER_VERSION_MAJOR 0u
-#define BPFLOADER_VERSION_MINOR 41u
-#define BPFLOADER_VERSION ((BPFLOADER_VERSION_MAJOR << 16) | BPFLOADER_VERSION_MINOR)
-
#include "BpfSyscallWrappers.h"
#include "bpf/BpfUtils.h"
#include "bpf/bpf_map_def.h"
#include "loader.h"
-#if BPFLOADER_VERSION < COMPILE_FOR_BPFLOADER_VERSION
-#error "BPFLOADER_VERSION is less than COMPILE_FOR_BPFLOADER_VERSION"
-#endif
-
#include <cstdlib>
#include <fstream>
#include <iostream>
@@ -178,32 +165,34 @@
* since they are less stable abi/api and may conflict with platform uses of bpf.
*/
sectionType sectionNameTypes[] = {
- {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
- {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
- {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
- {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
- {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
- {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
- {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
- {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
- {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
- {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
- {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
- {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
- {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
- {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
- {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
- {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
- {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
- {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
- {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
- {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
- {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
- {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
- {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
+ {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
+ {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
+ {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupsockcreate/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_CREATE},
+ {"cgroupsockrelease/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_RELEASE},
+ {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
+ {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
+ {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
+ {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
+ {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
+ {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
+ {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
+ {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
+ {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
+ {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
+ {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
+ {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
+ {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
+ {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
+ {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
+ {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
+ {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
+ {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
+ {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
};
typedef struct {
@@ -413,9 +402,6 @@
size_t sizeOfBpfProgDef) {
vector<char> pdData;
int ret = readSectionByName("progs", elfFile, pdData);
- // Older file formats do not require a 'progs' section at all.
- // (We should probably figure out whether this is behaviour which is safe to remove now.)
- if (ret == -2) return 0;
if (ret) return ret;
if (pdData.size() % sizeOfBpfProgDef) {
@@ -528,7 +514,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);
@@ -548,13 +534,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;
@@ -574,6 +560,14 @@
static bool mapMatchesExpectations(const unique_fd& fd, const string& mapName,
const struct bpf_map_def& mapDef, const enum bpf_map_type type) {
+ // bpfGetFd... family of functions require at minimum a 4.14 kernel,
+ // so on 4.9-T kernels just pretend the map matches our expectations.
+ // Additionally we'll get almost equivalent test coverage on newer devices/kernels.
+ // This is because the primary failure mode we're trying to detect here
+ // is either a source code misconfiguration (which is likely kernel independent)
+ // or a newly introduced kernel feature/bug (which is unlikely to get backported to 4.9).
+ if (!isAtLeastKernelVersion(4, 14, 0)) return true;
+
// Assuming fd is a valid Bpf Map file descriptor then
// all the following should always succeed on a 4.14+ kernel.
// If they somehow do fail, they'll return -1 (and set errno),
@@ -621,7 +615,8 @@
}
static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds,
- const char* prefix, const size_t sizeOfBpfMapDef) {
+ const char* prefix, const size_t sizeOfBpfMapDef,
+ const unsigned int bpfloader_ver) {
int ret;
vector<char> mdData;
vector<struct bpf_map_def> md;
@@ -663,14 +658,14 @@
for (int i = 0; i < (int)mapNames.size(); i++) {
if (md[i].zero != 0) abort();
- if (BPFLOADER_VERSION < md[i].bpfloader_min_ver) {
+ if (bpfloader_ver < md[i].bpfloader_min_ver) {
ALOGI("skipping map %s which requires bpfloader min ver 0x%05x", mapNames[i].c_str(),
md[i].bpfloader_min_ver);
mapFds.push_back(unique_fd());
continue;
}
- if (BPFLOADER_VERSION >= md[i].bpfloader_max_ver) {
+ if (bpfloader_ver >= md[i].bpfloader_max_ver) {
ALOGI("skipping map %s which requires bpfloader max ver 0x%05x", mapNames[i].c_str(),
md[i].bpfloader_max_ver);
mapFds.push_back(unique_fd());
@@ -711,6 +706,16 @@
}
enum bpf_map_type type = md[i].type;
+ if (type == BPF_MAP_TYPE_DEVMAP && !isAtLeastKernelVersion(4, 14, 0)) {
+ // On Linux Kernels older than 4.14 this map type doesn't exist, but it can kind
+ // of be approximated: ARRAY has the same userspace api, though it is not usable
+ // by the same ebpf programs. However, that's okay because the bpf_redirect_map()
+ // helper doesn't exist on 4.9-T anyway (so the bpf program would fail to load,
+ // and thus needs to be tagged as 4.14+ either way), so there's nothing useful you
+ // could do with a DEVMAP anyway (that isn't already provided by an ARRAY)...
+ // Hence using an ARRAY instead of a DEVMAP simply makes life easier for userspace.
+ type = BPF_MAP_TYPE_ARRAY;
+ }
if (type == BPF_MAP_TYPE_DEVMAP_HASH && !isAtLeastKernelVersion(5, 4, 0)) {
// On Linux Kernels older than 5.4 this map type doesn't exist, but it can kind
// of be approximated: HASH has the same userspace visible api.
@@ -733,15 +738,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>
@@ -766,7 +771,8 @@
.max_entries = max_entries,
.map_flags = md[i].map_flags,
};
- strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
+ 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;
ALOGD("bpf_create_map name %s, ret: %d", mapNames[i].c_str(), fd.get());
@@ -865,7 +871,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);
}
@@ -910,7 +916,7 @@
}
static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license,
- const char* prefix) {
+ const char* prefix, const unsigned int bpfloader_ver) {
unsigned kvers = kernelVersion();
if (!kvers) {
@@ -946,8 +952,8 @@
ALOGD("cs[%d].name:%s requires bpfloader version [0x%05x,0x%05x)", i, name.c_str(),
bpfMinVer, bpfMaxVer);
- if (BPFLOADER_VERSION < bpfMinVer) continue;
- if (BPFLOADER_VERSION >= bpfMaxVer) continue;
+ if (bpfloader_ver < bpfMinVer) continue;
+ if (bpfloader_ver >= bpfMaxVer) continue;
if ((cs[i].prog_def->ignore_on_eng && isEng()) ||
(cs[i].prog_def->ignore_on_user && isUser()) ||
@@ -970,13 +976,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
@@ -1008,7 +1015,8 @@
.log_size = static_cast<__u32>(log_buf.size()),
.expected_attach_type = cs[i].expected_attach_type,
};
- strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
+ 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));
ALOGD("BPF_PROG_LOAD call for %s (%s) returned fd: %d (%s)", elfPath,
@@ -1082,7 +1090,8 @@
return 0;
}
-int loadProg(const char* elfPath, bool* isCritical, const Location& location) {
+int loadProg(const char* const elfPath, bool* const isCritical, const unsigned int bpfloader_ver,
+ const Location& location) {
vector<char> license;
vector<char> critical;
vector<codeSection> cs;
@@ -1121,27 +1130,27 @@
readSectionUint("size_of_bpf_prog_def", elfFile, DEFAULT_SIZEOF_BPF_PROG_DEF);
// inclusive lower bound check
- if (BPFLOADER_VERSION < bpfLoaderMinVer) {
+ if (bpfloader_ver < bpfLoaderMinVer) {
ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with min ver 0x%05x",
- BPFLOADER_VERSION, elfPath, bpfLoaderMinVer);
+ bpfloader_ver, elfPath, bpfLoaderMinVer);
return 0;
}
// exclusive upper bound check
- if (BPFLOADER_VERSION >= bpfLoaderMaxVer) {
+ if (bpfloader_ver >= bpfLoaderMaxVer) {
ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with max ver 0x%05x",
- BPFLOADER_VERSION, elfPath, bpfLoaderMaxVer);
+ bpfloader_ver, elfPath, bpfLoaderMaxVer);
return 0;
}
- if (BPFLOADER_VERSION < bpfLoaderMinRequiredVer) {
+ if (bpfloader_ver < bpfLoaderMinRequiredVer) {
ALOGI("BpfLoader version 0x%05x failing due to ELF object %s with required min ver 0x%05x",
- BPFLOADER_VERSION, elfPath, bpfLoaderMinRequiredVer);
+ bpfloader_ver, elfPath, bpfLoaderMinRequiredVer);
return -1;
}
ALOGI("BpfLoader version 0x%05x processing ELF object %s with ver [0x%05x,0x%05x)",
- BPFLOADER_VERSION, elfPath, bpfLoaderMinVer, bpfLoaderMaxVer);
+ bpfloader_ver, elfPath, bpfLoaderMinVer, bpfLoaderMaxVer);
if (sizeOfBpfMapDef < DEFAULT_SIZEOF_BPF_MAP_DEF) {
ALOGE("sizeof(bpf_map_def) of %zu is too small (< %d)", sizeOfBpfMapDef,
@@ -1164,18 +1173,18 @@
/* Just for future debugging */
if (0) dumpAllCs(cs);
- ret = createMaps(elfPath, elfFile, mapFds, location.prefix, sizeOfBpfMapDef);
+ ret = createMaps(elfPath, elfFile, mapFds, location.prefix, sizeOfBpfMapDef, bpfloader_ver);
if (ret) {
ALOGE("Failed to create maps: (ret=%d) in %s", ret, elfPath);
return ret;
}
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);
- ret = loadCodeSections(elfPath, cs, string(license.data()), location.prefix);
+ ret = loadCodeSections(elfPath, cs, string(license.data()), location.prefix, bpfloader_ver);
if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d", ret);
return ret;
diff --git a/netbpfload/loader.h b/netbpfload/loader.h
index b884637..4da6830 100644
--- a/netbpfload/loader.h
+++ b/netbpfload/loader.h
@@ -70,7 +70,8 @@
};
// BPF loader implementation. Loads an eBPF ELF object
-int loadProg(const char* elfPath, bool* isCritical, const Location &location = {});
+int loadProg(const char* elfPath, bool* isCritical, const unsigned int bpfloader_ver,
+ const Location &location = {});
// Exposed for testing
unsigned int readSectionUint(const char* name, std::ifstream& elfFile, unsigned int defVal);
diff --git a/netbpfload/netbpfload.33rc b/netbpfload/netbpfload.33rc
new file mode 100644
index 0000000..493731f
--- /dev/null
+++ b/netbpfload/netbpfload.33rc
@@ -0,0 +1,21 @@
+# This file takes effect only on T and U (on V netbpfload.35rc takes priority).
+#
+# The service is started from netd's libnetd_updatable shared library
+# on initial (boot time) startup of netd.
+#
+# However we never start this service on U QPR3.
+#
+# This is due to lack of a need: U QPR2 split the previously single
+# platform bpfloader into platform netbpfload -> platform bpfloader.
+# U QPR3 made the platform netbpfload unconditionally exec apex netbpfload,
+# so by the time U QPR3's netd runs, apex netbpfload is already done.
+
+service mdnsd_netbpfload /apex/com.android.tethering/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
+ user system
+ file /dev/kmsg w
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,netbpfload-failed
+ override
diff --git a/netbpfload/netbpfload.mainline.rc b/netbpfload/netbpfload.35rc
similarity index 93%
rename from netbpfload/netbpfload.mainline.rc
rename to netbpfload/netbpfload.35rc
index 0ac5de8..0fbcb5a 100644
--- a/netbpfload/netbpfload.mainline.rc
+++ b/netbpfload/netbpfload.35rc
@@ -2,6 +2,7 @@
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
diff --git a/netbpfload/netbpfload.rc b/netbpfload/netbpfload.rc
index 14181dc..e1af47f 100644
--- a/netbpfload/netbpfload.rc
+++ b/netbpfload/netbpfload.rc
@@ -17,15 +17,18 @@
on load_bpf_programs
exec_start bpfloader
-service bpfloader /system/bin/netbpfload
+# Note: This will actually execute /apex/com.android.tethering/bin/netbpfload
+# by virtue of 'service bpfloader' being overridden by the apex shipped .rc
+# Warning: most of the below settings are irrelevant unless the apex is missing.
+service bpfloader /system/bin/false
# netbpfload will do network bpf loading, then execute /system/bin/bpfloader
- capabilities CHOWN SYS_ADMIN NET_ADMIN
+ #! capabilities CHOWN SYS_ADMIN NET_ADMIN
# The following group memberships are a workaround for lack of DAC_OVERRIDE
# and allow us to open (among other things) files that we created and are
# no longer root owned (due to CHOWN) but still have group read access to
# one of the following groups. This is not perfect, but a more correct
# solution requires significantly more effort to implement.
- group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+ #! group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
user root
#
# Set RLIMIT_MEMLOCK to 1GiB for bpfloader
@@ -55,7 +58,7 @@
#
# As such we simply use 1GiB as a reasonable approximation of infinity.
#
- rlimit memlock 1073741824 1073741824
+ #! rlimit memlock 1073741824 1073741824
oneshot
#
# How to debug bootloops caused by 'bpfloader-failed'.
@@ -81,6 +84,5 @@
# 'cannot prove return value is 0 or 1' or 'unsupported / unknown operation / helper',
# 'invalid bpf_context access', etc.
#
- reboot_on_failure reboot,bpfloader-failed
- # we're not really updatable, but want to be able to load bpf programs shipped in apexes
+ reboot_on_failure reboot,netbpfload-missing
updatable
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 a00c363..4779b47 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"
@@ -34,6 +34,7 @@
namespace net {
using base::unique_fd;
+using base::WaitForProperty;
using bpf::getSocketCookie;
using bpf::retrieveProgram;
using netdutils::Status;
@@ -85,31 +86,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");
@@ -134,8 +110,35 @@
// TODO: delete the if statement once all devices should support cgroup
// socket filter (ie. the minimum kernel version required is 4.14).
if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
- RETURN_IF_NOT_OK(
- attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_CREATE_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+ }
+
+ if (modules::sdklevel::IsAtLeastV()) {
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT4_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET4_CONNECT));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT6_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET6_CONNECT));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP4_RECVMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP4_RECVMSG));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP6_RECVMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP6_RECVMSG));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP4_SENDMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP4_SENDMSG));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP6_SENDMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP6_SENDMSG));
+
+ if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_GETSOCKOPT_PROG_PATH,
+ cg_fd, BPF_CGROUP_GETSOCKOPT));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SETSOCKOPT_PROG_PATH,
+ cg_fd, BPF_CGROUP_SETSOCKOPT));
+ }
+
+ if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_RELEASE_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET_SOCK_RELEASE));
+ }
}
if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
@@ -155,6 +158,24 @@
if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_BIND) <= 0) abort();
}
+ if (modules::sdklevel::IsAtLeastV()) {
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET4_CONNECT) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_CONNECT) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_RECVMSG) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_RECVMSG) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_SENDMSG) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_SENDMSG) <= 0) abort();
+
+ if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_GETSOCKOPT) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_SETSOCKOPT) <= 0) abort();
+ }
+
+ if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
+ }
+ }
+
return netdutils::status::ok;
}
@@ -165,9 +186,50 @@
BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
: mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+static bool mainlineNetBpfLoadDone() {
+ return !access("/sys/fs/bpf/netd_shared/mainline_done", F_OK);
+}
+
+// copied with minor changes from waitForProgsLoaded()
+// p/m/C's staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
+static inline void waitForNetProgsLoaded() {
+ // infinite loop until success with 5/10/20/40/60/60/60... delay
+ for (int delay = 5;; delay *= 2) {
+ if (delay > 60) delay = 60;
+ if (WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay))
+ && mainlineNetBpfLoadDone())
+ return;
+ ALOGW("Waited %ds for init.svc.mdnsd_netbpfload=stopped, still waiting...", delay);
+ }
+}
+
Status BpfHandler::init(const char* cg2_path) {
- // Make sure BPF programs are loaded before doing anything
- android::bpf::waitForProgsLoaded();
+ // Note: netd *can* be restarted, so this might get called a second time after boot is complete
+ // at which point we don't need to (and shouldn't) wait for (more importantly start) loading bpf
+
+ if (base::GetProperty("bpf.progs_loaded", "") != "1") {
+ // AOSP platform netd & mainline don't need this (at least prior to U QPR3),
+ // but there could be platform provided (xt_)bpf programs that oem/vendor
+ // modified netd (which calls us during init) depends on...
+ ALOGI("Waiting for platform BPF programs");
+ android::bpf::waitForProgsLoaded();
+ }
+
+ if (!mainlineNetBpfLoadDone()) {
+ // We're on < U QPR3 & it's the first time netd is starting up (unless crashlooping)
+ //
+ // On U QPR3+ netbpfload is guaranteed to run before the platform bpfloader,
+ // so waitForProgsLoaded() implies mainlineNetBpfLoadDone().
+ if (!base::SetProperty("ctl.start", "mdnsd_netbpfload")) {
+ ALOGE("Failed to set property ctl.start=mdnsd_netbpfload, see dmesg for reason.");
+ abort();
+ }
+
+ ALOGI("Waiting for Networking BPF programs");
+ waitForNetProgsLoaded();
+ ALOGI("Networking BPF programs are loaded");
+ }
+
ALOGI("BPF programs are loaded");
RETURN_IF_NOT_OK(initPrograms(cg2_path));
@@ -176,7 +238,30 @@
return netdutils::status::ok;
}
+static void mapLockTest(void) {
+ // The maps must be R/W, and as yet unopened (or more specifically not yet lock'ed).
+ const char * const m1 = BPF_NETD_PATH "map_netd_lock_array_test_map";
+ const char * const m2 = BPF_NETD_PATH "map_netd_lock_hash_test_map";
+
+ unique_fd fd0(bpf::mapRetrieveExclusiveRW(m1)); if (!fd0.ok()) abort(); // grabs exclusive lock
+
+ unique_fd fd1(bpf::mapRetrieveExclusiveRW(m2)); if (!fd1.ok()) abort(); // no conflict with fd0
+ unique_fd fd2(bpf::mapRetrieveExclusiveRW(m2)); if ( fd2.ok()) abort(); // busy due to fd1
+ unique_fd fd3(bpf::mapRetrieveRO(m2)); if (!fd3.ok()) abort(); // no lock taken
+ unique_fd fd4(bpf::mapRetrieveRW(m2)); if ( fd4.ok()) abort(); // busy due to fd1
+ fd1.reset(); // releases exclusive lock
+ unique_fd fd5(bpf::mapRetrieveRO(m2)); if (!fd5.ok()) abort(); // no lock taken
+ unique_fd fd6(bpf::mapRetrieveRW(m2)); if (!fd6.ok()) abort(); // now ok
+ unique_fd fd7(bpf::mapRetrieveRO(m2)); if (!fd7.ok()) abort(); // no lock taken
+ unique_fd fd8(bpf::mapRetrieveExclusiveRW(m2)); if ( fd8.ok()) abort(); // busy due to fd6
+
+ fd0.reset(); // releases exclusive lock
+ unique_fd fd9(bpf::mapRetrieveWO(m1)); if (!fd9.ok()) abort(); // grabs exclusive lock
+}
+
Status BpfHandler::initMaps() {
+ mapLockTest();
+
RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
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/Android.bp b/service-t/Android.bp
index 779f354..96efd18 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -100,7 +100,7 @@
min_sdk_version: "21",
lint: {
error_checks: ["NewApi"],
-
+ baseline_filename: "lint-baseline-service-connectivity-mdns-standalone-build-test.xml",
},
srcs: [
"src/com/android/server/connectivity/mdns/**/*.java",
diff --git a/service-t/lint-baseline-service-connectivity-mdns-standalone-build-test.xml b/service-t/lint-baseline-service-connectivity-mdns-standalone-build-test.xml
new file mode 100644
index 0000000..232d31c
--- /dev/null
+++ b/service-t/lint-baseline-service-connectivity-mdns-standalone-build-test.xml
@@ -0,0 +1,972 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+</issues>
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 cfb1a33..64624ae 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();
@@ -1841,16 +1923,18 @@
mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
.setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING))
- .setIsExpiredServicesRemovalEnabled(mDeps.isFeatureEnabled(
+ .setIsExpiredServicesRemovalEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
.setIsLabelCountLimitEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_LIMIT_LABEL_COUNT))
- .setIsKnownAnswerSuppressionEnabled(mDeps.isFeatureEnabled(
+ .setIsKnownAnswerSuppressionEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
- .setIsUnicastReplyEnabled(mDeps.isFeatureEnabled(
+ .setIsUnicastReplyEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_UNICAST_REPLY_ENABLED))
.setIsAggressiveQueryModeEnabled(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
+ .setIsQueryWithKnownAnswerEnabled(mDeps.isFeatureEnabled(
+ mContext, MdnsFeatureFlags.NSD_QUERY_WITH_KNOWN_ANSWER))
.setOverrideProvider(flag -> mDeps.isFeatureEnabled(
mContext, FORCE_ENABLE_FLAG_FOR_TEST_PREFIX + flag))
.build();
@@ -2543,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:");
@@ -2615,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 {
@@ -2624,6 +2734,12 @@
super(transactionId, startTimeMs);
mRequestCode = requestCode;
}
+
+ @NonNull
+ @Override
+ protected String getRequestDescriptor() {
+ return "Legacy (" + mRequestCode + ")";
+ }
}
private abstract static class JavaBackendClientRequest extends ClientRequest {
@@ -2643,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());
}
}
@@ -2658,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 */
@@ -2702,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();
@@ -2762,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()),
@@ -2801,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);
@@ -2923,7 +3058,8 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- request.getSentQueryCount());
+ request.getSentQueryCount(),
+ request.isServiceFromCache());
try {
mCb.onStopDiscoverySucceeded(listenerKey);
} catch (RemoteException e) {
@@ -3022,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/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index c4d3338..54943c7 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -23,6 +23,7 @@
import android.text.TextUtils;
import android.util.Pair;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -63,8 +64,6 @@
@NonNull
private final WeakReference<MdnsSocketClientBase> weakRequestSender;
@NonNull
- private final MdnsPacketWriter packetWriter;
- @NonNull
private final String[] serviceTypeLabels;
@NonNull
private final List<String> subtypes;
@@ -79,11 +78,16 @@
private final MdnsUtils.Clock clock;
@NonNull
private final SharedLog sharedLog;
+ @NonNull
+ private final MdnsServiceTypeClient.Dependencies dependencies;
private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
+ private final byte[] packetCreationBuffer = new byte[1500]; // TODO: use interface MTU
+ @NonNull
+ private final List<MdnsResponse> existingServices;
+ private final boolean isQueryWithKnownAnswer;
EnqueueMdnsQueryCallable(
@NonNull MdnsSocketClientBase requestSender,
- @NonNull MdnsPacketWriter packetWriter,
@NonNull String serviceType,
@NonNull Collection<String> subtypes,
boolean expectUnicastResponse,
@@ -93,9 +97,11 @@
boolean sendDiscoveryQueries,
@NonNull Collection<MdnsResponse> servicesToResolve,
@NonNull MdnsUtils.Clock clock,
- @NonNull SharedLog sharedLog) {
+ @NonNull SharedLog sharedLog,
+ @NonNull MdnsServiceTypeClient.Dependencies dependencies,
+ @NonNull Collection<MdnsResponse> existingServices,
+ boolean isQueryWithKnownAnswer) {
weakRequestSender = new WeakReference<>(requestSender);
- this.packetWriter = packetWriter;
serviceTypeLabels = TextUtils.split(serviceType, "\\.");
this.subtypes = new ArrayList<>(subtypes);
this.expectUnicastResponse = expectUnicastResponse;
@@ -106,6 +112,9 @@
this.servicesToResolve = new ArrayList<>(servicesToResolve);
this.clock = clock;
this.sharedLog = sharedLog;
+ this.dependencies = dependencies;
+ this.existingServices = new ArrayList<>(existingServices);
+ this.isQueryWithKnownAnswer = isQueryWithKnownAnswer;
}
/**
@@ -176,62 +185,86 @@
return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
}
+ // Put the existing ptr records into known-answer section.
+ final List<MdnsRecord> knownAnswers = new ArrayList<>();
+ if (sendDiscoveryQueries) {
+ for (MdnsResponse existingService : existingServices) {
+ for (MdnsPointerRecord ptrRecord : existingService.getPointerRecords()) {
+ // Ignore any PTR records that don't match the current query.
+ if (!CollectionUtils.any(questions,
+ q -> q instanceof MdnsPointerRecord
+ && MdnsUtils.equalsDnsLabelIgnoreDnsCase(
+ q.getName(), ptrRecord.getName()))) {
+ continue;
+ }
+
+ knownAnswers.add(new MdnsPointerRecord(
+ ptrRecord.getName(),
+ ptrRecord.getReceiptTime(),
+ ptrRecord.getCacheFlush(),
+ ptrRecord.getRemainingTTL(now), // Put the remaining ttl.
+ ptrRecord.getPointer()));
+ }
+ }
+ }
+
final MdnsPacket queryPacket = new MdnsPacket(
transactionId,
MdnsConstants.FLAGS_QUERY,
questions,
- Collections.emptyList(), /* answers */
+ knownAnswers,
Collections.emptyList(), /* authorityRecords */
Collections.emptyList() /* additionalRecords */);
- MdnsUtils.writeMdnsPacket(packetWriter, queryPacket);
- sendPacketToIpv4AndIpv6(requestSender, MdnsConstants.MDNS_PORT);
+ sendPacketToIpv4AndIpv6(requestSender, MdnsConstants.MDNS_PORT, queryPacket);
for (Integer emulatorPort : castShellEmulatorMdnsPorts) {
- sendPacketToIpv4AndIpv6(requestSender, emulatorPort);
+ sendPacketToIpv4AndIpv6(requestSender, emulatorPort, queryPacket);
}
return Pair.create(transactionId, subtypes);
- } catch (IOException e) {
+ } catch (Exception e) {
sharedLog.e(String.format("Failed to create mDNS packet for subtype: %s.",
TextUtils.join(",", subtypes)), e);
return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
}
}
- private void sendPacket(MdnsSocketClientBase requestSender, InetSocketAddress address)
- throws IOException {
- DatagramPacket packet = packetWriter.getPacket(address);
+ private void sendPacket(MdnsSocketClientBase requestSender, InetSocketAddress address,
+ MdnsPacket mdnsPacket) throws IOException {
+ final List<DatagramPacket> packets = dependencies.getDatagramPacketsFromMdnsPacket(
+ packetCreationBuffer, mdnsPacket, address, isQueryWithKnownAnswer);
if (expectUnicastResponse) {
// MdnsMultinetworkSocketClient is only available on T+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& requestSender instanceof MdnsMultinetworkSocketClient) {
((MdnsMultinetworkSocketClient) requestSender).sendPacketRequestingUnicastResponse(
- packet, socketKey, onlyUseIpv6OnIpv6OnlyNetworks);
+ packets, socketKey, onlyUseIpv6OnIpv6OnlyNetworks);
} else {
requestSender.sendPacketRequestingUnicastResponse(
- packet, onlyUseIpv6OnIpv6OnlyNetworks);
+ packets, onlyUseIpv6OnIpv6OnlyNetworks);
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& requestSender instanceof MdnsMultinetworkSocketClient) {
((MdnsMultinetworkSocketClient) requestSender)
.sendPacketRequestingMulticastResponse(
- packet, socketKey, onlyUseIpv6OnIpv6OnlyNetworks);
+ packets, socketKey, onlyUseIpv6OnIpv6OnlyNetworks);
} else {
requestSender.sendPacketRequestingMulticastResponse(
- packet, onlyUseIpv6OnIpv6OnlyNetworks);
+ packets, onlyUseIpv6OnIpv6OnlyNetworks);
}
}
}
- private void sendPacketToIpv4AndIpv6(MdnsSocketClientBase requestSender, int port) {
+ private void sendPacketToIpv4AndIpv6(MdnsSocketClientBase requestSender, int port,
+ MdnsPacket mdnsPacket) {
try {
sendPacket(requestSender,
- new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), port));
+ new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), port), mdnsPacket);
} catch (IOException e) {
sharedLog.e("Can't send packet to IPv4", e);
}
try {
sendPacket(requestSender,
- new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), port));
+ new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), port), mdnsPacket);
} catch (IOException e) {
sharedLog.e("Can't send packet to IPv6", 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 c162bcc..b870477 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -241,13 +241,10 @@
}
@Override
- public void onDestroyed(@NonNull MdnsInterfaceSocket socket) {
- for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) {
- if (mAdvertiserRequests.valueAt(i).onAdvertiserDestroyed(socket)) {
- mAdvertiserRequests.removeAt(i);
- }
- }
- mAllAdvertisers.remove(socket);
+ public void onAllServicesRemoved(@NonNull MdnsInterfaceSocket socket) {
+ if (DBG) { mSharedLog.i("onAllServicesRemoved: " + socket); }
+ // Try destroying the advertiser if all services has been removed
+ destroyAdvertiser(socket, false /* interfaceDestroyed */);
}
};
@@ -318,6 +315,30 @@
}
/**
+ * Destroys the advertiser for the interface indicated by {@code socket}.
+ *
+ * {@code interfaceDestroyed} should be set to {@code true} if this method is called because
+ * the associated interface has been destroyed.
+ */
+ private void destroyAdvertiser(MdnsInterfaceSocket socket, boolean interfaceDestroyed) {
+ InterfaceAdvertiserRequest advertiserRequest;
+
+ MdnsInterfaceAdvertiser advertiser = mAllAdvertisers.remove(socket);
+ if (advertiser != null) {
+ advertiser.destroyNow();
+ if (DBG) { mSharedLog.i("MdnsInterfaceAdvertiser is destroyed: " + advertiser); }
+ }
+
+ for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) {
+ advertiserRequest = mAdvertiserRequests.valueAt(i);
+ if (advertiserRequest.onAdvertiserDestroyed(socket, interfaceDestroyed)) {
+ if (DBG) { mSharedLog.i("AdvertiserRequest is removed: " + advertiserRequest); }
+ mAdvertiserRequests.removeAt(i);
+ }
+ }
+ }
+
+ /**
* A request for a {@link MdnsInterfaceAdvertiser}.
*
* This class tracks services to be advertised on all sockets provided via a registered
@@ -336,13 +357,22 @@
}
/**
- * Called when an advertiser was destroyed, after all services were unregistered and it sent
- * exit announcements, or the interface is gone.
+ * Called when the interface advertiser associated with {@code socket} has been destroyed.
*
- * @return true if this {@link InterfaceAdvertiserRequest} should now be deleted.
+ * {@code interfaceDestroyed} should be set to {@code true} if this method is called because
+ * the associated interface has been destroyed.
+ *
+ * @return true if the {@link InterfaceAdvertiserRequest} should now be deleted
*/
- boolean onAdvertiserDestroyed(@NonNull MdnsInterfaceSocket socket) {
+ boolean onAdvertiserDestroyed(
+ @NonNull MdnsInterfaceSocket socket, boolean interfaceDestroyed) {
final MdnsInterfaceAdvertiser removedAdvertiser = mAdvertisers.remove(socket);
+ if (removedAdvertiser != null
+ && !interfaceDestroyed && mPendingRegistrations.size() > 0) {
+ mSharedLog.wtf(
+ "unexpected onAdvertiserDestroyed() when there are pending registrations");
+ }
+
if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled && removedAdvertiser != null) {
final String interfaceName = removedAdvertiser.getSocketInterfaceName();
// If the interface is destroyed, stop all hardware offloading on that
@@ -418,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.
+ * UID, 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())) {
@@ -430,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;
@@ -528,7 +566,7 @@
public void onInterfaceDestroyed(@NonNull SocketKey socketKey,
@NonNull MdnsInterfaceSocket socket) {
final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket);
- if (advertiser != null) advertiser.destroyNow();
+ if (advertiser != null) destroyAdvertiser(socket, true /* interfaceDestroyed */);
}
@Override
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 21b7069..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 =
@@ -362,7 +381,7 @@
return new MdnsServiceTypeClient(
serviceType, socketClient,
executorProvider.newServiceTypeClientSchedulerExecutor(), socketKey,
- sharedLog.forSubComponent(tag), looper, serviceCache);
+ sharedLog.forSubComponent(tag), looper, serviceCache, mdnsFeatureFlags);
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index 56202fd..c264f25 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -62,6 +62,11 @@
*/
public static final String NSD_AGGRESSIVE_QUERY_MODE = "nsd_aggressive_query_mode";
+ /**
+ * A feature flag to control whether the query with known-answer should be enabled.
+ */
+ public static final String NSD_QUERY_WITH_KNOWN_ANSWER = "nsd_query_with_known_answer";
+
// Flag for offload feature
public final boolean mIsMdnsOffloadFeatureEnabled;
@@ -83,6 +88,9 @@
// Flag for aggressive query mode
public final boolean mIsAggressiveQueryModeEnabled;
+ // Flag for query with known-answer
+ public final boolean mIsQueryWithKnownAnswerEnabled;
+
@Nullable
private final FlagOverrideProvider mOverrideProvider;
@@ -126,6 +134,14 @@
}
/**
+ * Indicates whether {@link #NSD_QUERY_WITH_KNOWN_ANSWER} is enabled, including for testing.
+ */
+ public boolean isQueryWithKnownAnswerEnabled() {
+ return mIsQueryWithKnownAnswerEnabled
+ || isForceEnabledForTest(NSD_QUERY_WITH_KNOWN_ANSWER);
+ }
+
+ /**
* The constructor for {@link MdnsFeatureFlags}.
*/
public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
@@ -135,6 +151,7 @@
boolean isKnownAnswerSuppressionEnabled,
boolean isUnicastReplyEnabled,
boolean isAggressiveQueryModeEnabled,
+ boolean isQueryWithKnownAnswerEnabled,
@Nullable FlagOverrideProvider overrideProvider) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
@@ -143,6 +160,7 @@
mIsKnownAnswerSuppressionEnabled = isKnownAnswerSuppressionEnabled;
mIsUnicastReplyEnabled = isUnicastReplyEnabled;
mIsAggressiveQueryModeEnabled = isAggressiveQueryModeEnabled;
+ mIsQueryWithKnownAnswerEnabled = isQueryWithKnownAnswerEnabled;
mOverrideProvider = overrideProvider;
}
@@ -162,6 +180,7 @@
private boolean mIsKnownAnswerSuppressionEnabled;
private boolean mIsUnicastReplyEnabled;
private boolean mIsAggressiveQueryModeEnabled;
+ private boolean mIsQueryWithKnownAnswerEnabled;
private FlagOverrideProvider mOverrideProvider;
/**
@@ -170,11 +189,12 @@
public Builder() {
mIsMdnsOffloadFeatureEnabled = false;
mIncludeInetAddressRecordsInProbing = false;
- mIsExpiredServicesRemovalEnabled = false;
+ mIsExpiredServicesRemovalEnabled = true; // Default enabled.
mIsLabelCountLimitEnabled = true; // Default enabled.
- mIsKnownAnswerSuppressionEnabled = false;
- mIsUnicastReplyEnabled = true;
+ mIsKnownAnswerSuppressionEnabled = true; // Default enabled.
+ mIsUnicastReplyEnabled = true; // Default enabled.
mIsAggressiveQueryModeEnabled = false;
+ mIsQueryWithKnownAnswerEnabled = false;
mOverrideProvider = null;
}
@@ -261,6 +281,16 @@
}
/**
+ * Set whether the query with known-answer is enabled.
+ *
+ * @see #NSD_QUERY_WITH_KNOWN_ANSWER
+ */
+ public Builder setIsQueryWithKnownAnswerEnabled(boolean isQueryWithKnownAnswerEnabled) {
+ mIsQueryWithKnownAnswerEnabled = isQueryWithKnownAnswerEnabled;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
public MdnsFeatureFlags build() {
@@ -271,6 +301,7 @@
mIsKnownAnswerSuppressionEnabled,
mIsUnicastReplyEnabled,
mIsAggressiveQueryModeEnabled,
+ mIsQueryWithKnownAnswerEnabled,
mOverrideProvider);
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index c2363c0..0b2003f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -22,12 +22,10 @@
import android.annotation.Nullable;
import android.annotation.RequiresApi;
import android.net.LinkAddress;
-import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.HexDump;
@@ -38,6 +36,7 @@
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -102,12 +101,15 @@
@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId, int conflictType);
/**
- * Called by the advertiser when it destroyed itself.
+ * Called when all services on this interface advertiser has already been removed and exit
+ * announcements have been sent.
*
- * This can happen after a call to {@link #destroyNow()}, or after all services were
- * unregistered and the advertiser finished sending exit announcements.
+ * <p>It's guaranteed that there are no service registrations in the
+ * MdnsInterfaceAdvertiser when this callback is invoked.
+ *
+ * <p>This is typically listened by the {@link MdnsAdvertiser} to release the resources
*/
- void onDestroyed(@NonNull MdnsInterfaceSocket socket);
+ void onAllServicesRemoved(@NonNull MdnsInterfaceSocket socket);
}
/**
@@ -133,6 +135,15 @@
mAnnouncer.startSending(info.getServiceId(), announcementInfo,
0L /* initialDelayMs */);
+
+ // Re-announce the services which have the same custom hostname.
+ final String hostname = mRecordRepository.getHostnameForServiceId(info.getServiceId());
+ if (hostname != null) {
+ final List<MdnsAnnouncer.AnnouncementInfo> announcementInfos =
+ new ArrayList<>(mRecordRepository.restartAnnouncingForHostname(hostname));
+ announcementInfos.removeIf((i) -> i.getServiceId() == info.getServiceId());
+ reannounceServices(announcementInfos);
+ }
}
}
@@ -149,10 +160,11 @@
public void onFinished(@NonNull BaseAnnouncementInfo info) {
if (info instanceof MdnsAnnouncer.ExitAnnouncementInfo) {
mRecordRepository.removeService(info.getServiceId());
-
- if (mRecordRepository.getServicesCount() == 0) {
- destroyNow();
- }
+ mCbHandler.post(() -> {
+ if (mRecordRepository.getServicesCount() == 0) {
+ mCb.onAllServicesRemoved(mSocket);
+ }
+ });
}
}
}
@@ -234,8 +246,7 @@
* Start the advertiser.
*
* The advertiser will stop itself when all services are removed and exit announcements sent,
- * notifying via {@link Callback#onDestroyed}. This can also be triggered manually via
- * {@link #destroyNow()}.
+ * notifying via {@link Callback#onAllServicesRemoved}.
*/
public void start() {
mSocket.addPacketHandler(this);
@@ -281,10 +292,11 @@
if (!mRecordRepository.hasActiveService(id)) return;
mProber.stop(id);
mAnnouncer.stop(id);
+ final String hostname = mRecordRepository.getHostnameForServiceId(id);
final MdnsAnnouncer.ExitAnnouncementInfo exitInfo = mRecordRepository.exitService(id);
if (exitInfo != null) {
- // This effectively schedules destroyNow(), as it is to be called when the exit
- // announcement finishes if there is no service left.
+ // This effectively schedules onAllServicesRemoved(), as it is to be called when the
+ // exit announcement finishes if there is no service left.
// A non-zero exit announcement delay follows legacy mdnsresponder behavior, and is
// also useful to ensure that when a host receives the exit announcement, the service
// has been unregistered on all interfaces; so an announcement sent from interface A
@@ -294,9 +306,22 @@
} else {
// No exit announcement necessary: remove the service immediately.
mRecordRepository.removeService(id);
- if (mRecordRepository.getServicesCount() == 0) {
- destroyNow();
- }
+ mCbHandler.post(() -> {
+ if (mRecordRepository.getServicesCount() == 0) {
+ mCb.onAllServicesRemoved(mSocket);
+ }
+ });
+ }
+ // Re-probe/re-announce the services which have the same custom hostname. These services
+ // were probed/announced using host addresses which were just removed so they should be
+ // re-probed/re-announced without those addresses.
+ if (hostname != null) {
+ final List<MdnsProber.ProbingInfo> probingInfos =
+ mRecordRepository.restartProbingForHostname(hostname);
+ reprobeServices(probingInfos);
+ final List<MdnsAnnouncer.AnnouncementInfo> announcementInfos =
+ mRecordRepository.restartAnnouncingForHostname(hostname);
+ reannounceServices(announcementInfos);
}
}
@@ -330,7 +355,8 @@
/**
* Destroy the advertiser immediately, not sending any exit announcement.
*
- * <p>Useful when the underlying network went away. This will trigger an onDestroyed callback.
+ * <p>This is typically called when all services on the interface are removed or when the
+ * underlying network went away.
*/
public void destroyNow() {
for (int serviceId : mRecordRepository.clearServices()) {
@@ -339,7 +365,6 @@
}
mReplySender.cancelAll();
mSocket.removePacketHandler(this);
- mCbHandler.post(() -> mCb.onDestroyed(mSocket));
}
/**
@@ -442,4 +467,19 @@
return new byte[0];
}
}
+
+ private void reprobeServices(List<MdnsProber.ProbingInfo> probingInfos) {
+ for (MdnsProber.ProbingInfo probingInfo : probingInfos) {
+ mProber.stop(probingInfo.getServiceId());
+ mProber.startProbing(probingInfo);
+ }
+ }
+
+ private void reannounceServices(List<MdnsAnnouncer.AnnouncementInfo> announcementInfos) {
+ for (MdnsAnnouncer.AnnouncementInfo announcementInfo : announcementInfos) {
+ mAnnouncer.stop(announcementInfo.getServiceId());
+ mAnnouncer.startSending(
+ announcementInfo.getServiceId(), announcementInfo, 0 /* initialDelayMs */);
+ }
+ }
}
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/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index 869ac9b..fcfb15f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
+import android.util.Log;
import com.android.net.module.util.SharedLog;
@@ -213,24 +214,30 @@
return true;
}
- private void sendMdnsPacket(@NonNull DatagramPacket packet, @NonNull SocketKey targetSocketKey,
- boolean onlyUseIpv6OnIpv6OnlyNetworks) {
+ private void sendMdnsPackets(@NonNull List<DatagramPacket> packets,
+ @NonNull SocketKey targetSocketKey, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
final MdnsInterfaceSocket socket = getTargetSocket(targetSocketKey);
if (socket == null) {
mSharedLog.e("No socket matches targetSocketKey=" + targetSocketKey);
return;
}
+ if (packets.isEmpty()) {
+ Log.wtf(TAG, "No mDns packets to send");
+ return;
+ }
- final boolean isIpv6 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
- instanceof Inet6Address;
- final boolean isIpv4 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
- instanceof Inet4Address;
+ final boolean isIpv6 = ((InetSocketAddress) packets.get(0).getSocketAddress())
+ .getAddress() instanceof Inet6Address;
+ final boolean isIpv4 = ((InetSocketAddress) packets.get(0).getSocketAddress())
+ .getAddress() instanceof Inet4Address;
final boolean shouldQueryIpv6 = !onlyUseIpv6OnIpv6OnlyNetworks || !socket.hasJoinedIpv4();
// Check ip capability and network before sending packet
if ((isIpv6 && socket.hasJoinedIpv6() && shouldQueryIpv6)
|| (isIpv4 && socket.hasJoinedIpv4())) {
try {
- socket.send(packet);
+ for (DatagramPacket packet : packets) {
+ socket.send(packet);
+ }
} catch (IOException e) {
mSharedLog.e("Failed to send a mDNS packet.", e);
}
@@ -259,34 +266,34 @@
}
/**
- * Send a mDNS request packet via given socket key that asks for multicast response.
+ * Send mDNS request packets via given socket key that asks for multicast response.
*/
- public void sendPacketRequestingMulticastResponse(@NonNull DatagramPacket packet,
+ public void sendPacketRequestingMulticastResponse(@NonNull List<DatagramPacket> packets,
@NonNull SocketKey socketKey, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
- mHandler.post(() -> sendMdnsPacket(packet, socketKey, onlyUseIpv6OnIpv6OnlyNetworks));
+ mHandler.post(() -> sendMdnsPackets(packets, socketKey, onlyUseIpv6OnIpv6OnlyNetworks));
}
@Override
public void sendPacketRequestingMulticastResponse(
- @NonNull DatagramPacket packet, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
+ @NonNull List<DatagramPacket> packets, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
throw new UnsupportedOperationException("This socket client need to specify the socket to"
+ "send packet");
}
/**
- * Send a mDNS request packet via given socket key that asks for unicast response.
+ * Send mDNS request packets via given socket key that asks for unicast response.
*
* <p>The socket client may use a null network to identify some or all interfaces, in which case
* passing null sends the packet to these.
*/
- public void sendPacketRequestingUnicastResponse(@NonNull DatagramPacket packet,
+ public void sendPacketRequestingUnicastResponse(@NonNull List<DatagramPacket> packets,
@NonNull SocketKey socketKey, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
- mHandler.post(() -> sendMdnsPacket(packet, socketKey, onlyUseIpv6OnIpv6OnlyNetworks));
+ mHandler.post(() -> sendMdnsPackets(packets, socketKey, onlyUseIpv6OnIpv6OnlyNetworks));
}
@Override
public void sendPacketRequestingUnicastResponse(
- @NonNull DatagramPacket packet, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
+ @NonNull List<DatagramPacket> packets, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
throw new UnsupportedOperationException("This socket client need to specify the socket to"
+ "send packet");
}
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 1fabd49..aef8211 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
@@ -42,7 +42,7 @@
@NonNull
public final List<MdnsRecord> additionalRecords;
- MdnsPacket(int flags,
+ public MdnsPacket(int flags,
@NonNull List<MdnsRecord> questions,
@NonNull List<MdnsRecord> answers,
@NonNull List<MdnsRecord> authorityRecords,
@@ -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 4b43989..b865319 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
@@ -23,7 +23,8 @@
import android.os.SystemClock;
import android.text.TextUtils;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
+
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
@@ -40,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;
@@ -231,7 +233,7 @@
* @param writer The writer to use.
* @param now The current system time. This is used when writing the updated TTL.
*/
- @VisibleForTesting
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public final void write(MdnsPacketWriter writer, long now) throws IOException {
writeHeaderFields(writer);
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 ac64c3a..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);
@@ -925,22 +1014,79 @@
}
}
+ @Nullable
+ public String getHostnameForServiceId(int id) {
+ ServiceRegistration registration = mServices.get(id);
+ if (registration == null) {
+ return null;
+ }
+ return registration.serviceInfo.getHostname();
+ }
+
+ /**
+ * Restart probing the services which are being probed and using the given custom hostname.
+ *
+ * @return The list of {@link MdnsProber.ProbingInfo} to be used by advertiser.
+ */
+ public List<MdnsProber.ProbingInfo> restartProbingForHostname(@NonNull String hostname) {
+ final ArrayList<MdnsProber.ProbingInfo> probingInfos = new ArrayList<>();
+ forEachActiveServiceRegistrationWithHostname(
+ hostname,
+ (id, registration) -> {
+ if (!registration.isProbing) {
+ return;
+ }
+ probingInfos.add(makeProbingInfo(id, registration));
+ });
+ return probingInfos;
+ }
+
+ /**
+ * Restart announcing the services which are using the given custom hostname.
+ *
+ * @return The list of {@link MdnsAnnouncer.AnnouncementInfo} to be used by advertiser.
+ */
+ public List<MdnsAnnouncer.AnnouncementInfo> restartAnnouncingForHostname(
+ @NonNull String hostname) {
+ final ArrayList<MdnsAnnouncer.AnnouncementInfo> announcementInfos = new ArrayList<>();
+ forEachActiveServiceRegistrationWithHostname(
+ hostname,
+ (id, registration) -> {
+ if (registration.isProbing) {
+ return;
+ }
+ announcementInfos.add(makeAnnouncementInfo(id, registration));
+ });
+ return announcementInfos;
+ }
+
/**
* Called to indicate that probing succeeded for a service.
+ *
* @param probeSuccessInfo The successful probing info.
* @return The {@link MdnsAnnouncer.AnnouncementInfo} to send, now that probing has succeeded.
*/
public MdnsAnnouncer.AnnouncementInfo onProbingSucceeded(
- MdnsProber.ProbingInfo probeSuccessInfo)
- throws IOException {
-
- int serviceId = probeSuccessInfo.getServiceId();
+ MdnsProber.ProbingInfo probeSuccessInfo) throws IOException {
+ final int serviceId = probeSuccessInfo.getServiceId();
final ServiceRegistration registration = mServices.get(serviceId);
if (registration == null) {
throw new IOException("Service is not registered: " + serviceId);
}
registration.setProbing(false);
+ return makeAnnouncementInfo(serviceId, registration);
+ }
+
+ /**
+ * Make the announcement info of the given service ID.
+ *
+ * @param serviceId The service ID.
+ * @param registration The service registration.
+ * @return The {@link MdnsAnnouncer.AnnouncementInfo} of the given service ID.
+ */
+ private MdnsAnnouncer.AnnouncementInfo makeAnnouncementInfo(
+ int serviceId, ServiceRegistration registration) {
final Set<MdnsRecord> answersSet = new LinkedHashSet<>();
final ArrayList<MdnsRecord> additionalAnswers = new ArrayList<>();
@@ -972,8 +1118,8 @@
addNsecRecordsForUniqueNames(additionalAnswers,
mGeneralRecords.iterator(), registration.allRecords.iterator());
- return new MdnsAnnouncer.AnnouncementInfo(
- probeSuccessInfo.getServiceId(), new ArrayList<>(answersSet), additionalAnswers);
+ return new MdnsAnnouncer.AnnouncementInfo(serviceId,
+ new ArrayList<>(answersSet), additionalAnswers);
}
/**
@@ -1013,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;
}
@@ -1036,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(
@@ -1245,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;
}
@@ -1313,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/connectivity/mdns/MdnsServiceInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index f60a95e..1ec9e39 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -94,57 +94,6 @@
@NonNull
private final Instant expirationTime;
- /** Constructs a {@link MdnsServiceInfo} object with default values. */
- public MdnsServiceInfo(
- String serviceInstanceName,
- String[] serviceType,
- @Nullable List<String> subtypes,
- String[] hostName,
- int port,
- @Nullable String ipv4Address,
- @Nullable String ipv6Address,
- @Nullable List<String> textStrings) {
- this(
- serviceInstanceName,
- serviceType,
- subtypes,
- hostName,
- port,
- List.of(ipv4Address),
- List.of(ipv6Address),
- textStrings,
- /* textEntries= */ null,
- /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
- /* network= */ null,
- /* expirationTime= */ Instant.MAX);
- }
-
- /** Constructs a {@link MdnsServiceInfo} object with default values. */
- public MdnsServiceInfo(
- String serviceInstanceName,
- String[] serviceType,
- List<String> subtypes,
- String[] hostName,
- int port,
- @Nullable String ipv4Address,
- @Nullable String ipv6Address,
- @Nullable List<String> textStrings,
- @Nullable List<TextEntry> textEntries) {
- this(
- serviceInstanceName,
- serviceType,
- subtypes,
- hostName,
- port,
- List.of(ipv4Address),
- List.of(ipv6Address),
- textStrings,
- textEntries,
- /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
- /* network= */ null,
- /* expirationTime= */ Instant.MAX);
- }
-
/**
* Constructs a {@link MdnsServiceInfo} object with default values.
*
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 8f41b94..b3bdbe0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -30,14 +30,18 @@
import android.util.ArrayMap;
import android.util.Pair;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
+
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
+import java.io.IOException;
import java.io.PrintWriter;
+import java.net.DatagramPacket;
import java.net.Inet4Address;
import java.net.Inet6Address;
+import java.net.InetSocketAddress;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
@@ -55,7 +59,6 @@
public class MdnsServiceTypeClient {
private static final String TAG = MdnsServiceTypeClient.class.getSimpleName();
- private static final int DEFAULT_MTU = 1500;
@VisibleForTesting
static final int EVENT_START_QUERYTASK = 1;
static final int EVENT_QUERY_RESULT = 2;
@@ -84,6 +87,7 @@
notifyRemovedServiceToListeners(previousResponse, "Service record expired");
}
};
+ @NonNull private final MdnsFeatureFlags featureFlags;
private final ArrayMap<MdnsServiceBrowserListener, ListenerInfo> listeners =
new ArrayMap<>();
private final boolean removeServiceAfterTtlExpires =
@@ -142,7 +146,8 @@
// before sending the query, it needs to be called just before sending it.
final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
final QueryTask queryTask = new QueryTask(taskArgs, servicesToResolve,
- getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners));
+ getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners),
+ getExistingServices());
executor.submit(queryTask);
break;
}
@@ -191,7 +196,7 @@
/**
* Dependencies of MdnsServiceTypeClient, for injection in tests.
*/
- @VisibleForTesting
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public static class Dependencies {
/**
* @see Handler#sendMessageDelayed(Message, long)
@@ -221,6 +226,25 @@
public void sendMessage(@NonNull Handler handler, @NonNull Message message) {
handler.sendMessage(message);
}
+
+ /**
+ * Generate the DatagramPackets from given MdnsPacket and InetSocketAddress.
+ *
+ * <p> If the query with known answer feature is enabled and the MdnsPacket is too large for
+ * a single DatagramPacket, it will be split into multiple DatagramPackets.
+ */
+ public List<DatagramPacket> getDatagramPacketsFromMdnsPacket(
+ @NonNull byte[] packetCreationBuffer, @NonNull MdnsPacket packet,
+ @NonNull InetSocketAddress address, boolean isQueryWithKnownAnswer)
+ throws IOException {
+ if (isQueryWithKnownAnswer) {
+ return MdnsUtils.createQueryDatagramPackets(packetCreationBuffer, packet, address);
+ } else {
+ final byte[] queryBuffer =
+ MdnsUtils.createRawDnsPacket(packetCreationBuffer, packet);
+ return List.of(new DatagramPacket(queryBuffer, 0, queryBuffer.length, address));
+ }
+ }
}
/**
@@ -236,9 +260,10 @@
@NonNull SocketKey socketKey,
@NonNull SharedLog sharedLog,
@NonNull Looper looper,
- @NonNull MdnsServiceCache serviceCache) {
+ @NonNull MdnsServiceCache serviceCache,
+ @NonNull MdnsFeatureFlags featureFlags) {
this(serviceType, socketClient, executor, new Clock(), socketKey, sharedLog, looper,
- new Dependencies(), serviceCache);
+ new Dependencies(), serviceCache, featureFlags);
}
@VisibleForTesting
@@ -251,7 +276,8 @@
@NonNull SharedLog sharedLog,
@NonNull Looper looper,
@NonNull Dependencies dependencies,
- @NonNull MdnsServiceCache serviceCache) {
+ @NonNull MdnsServiceCache serviceCache,
+ @NonNull MdnsFeatureFlags featureFlags) {
this.serviceType = serviceType;
this.socketClient = socketClient;
this.executor = executor;
@@ -265,6 +291,7 @@
this.serviceCache = serviceCache;
this.mdnsQueryScheduler = new MdnsQueryScheduler();
this.cacheKey = new MdnsServiceCache.CacheKey(serviceType, socketKey);
+ this.featureFlags = featureFlags;
}
/**
@@ -327,6 +354,11 @@
now.plusMillis(response.getMinRemainingTtl(now.toEpochMilli())));
}
+ private List<MdnsResponse> getExistingServices() {
+ return featureFlags.isQueryWithKnownAnswerEnabled()
+ ? serviceCache.getCachedServices(cacheKey) : Collections.emptyList();
+ }
+
/**
* Registers {@code listener} for receiving discovery event of mDNS service instances, and
* starts
@@ -391,7 +423,8 @@
final QueryTask queryTask = new QueryTask(
mdnsQueryScheduler.scheduleFirstRun(taskConfig, now,
minRemainingTtl, currentSessionId), servicesToResolve,
- getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners));
+ getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners),
+ getExistingServices());
executor.submit(queryTask);
}
@@ -617,11 +650,6 @@
return searchOptions != null && searchOptions.removeExpiredService();
}
- @VisibleForTesting
- MdnsPacketWriter createMdnsPacketWriter() {
- return new MdnsPacketWriter(DEFAULT_MTU);
- }
-
private List<MdnsResponse> makeResponsesForResolve(@NonNull SocketKey socketKey) {
final List<MdnsResponse> resolveResponses = new ArrayList<>();
for (int i = 0; i < listeners.size(); i++) {
@@ -694,14 +722,16 @@
private final List<MdnsResponse> servicesToResolve = new ArrayList<>();
private final List<String> subtypes = new ArrayList<>();
private final boolean sendDiscoveryQueries;
+ private final List<MdnsResponse> existingServices = new ArrayList<>();
QueryTask(@NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs,
@NonNull Collection<MdnsResponse> servicesToResolve,
- @NonNull Collection<String> subtypes,
- boolean sendDiscoveryQueries) {
+ @NonNull Collection<String> subtypes, boolean sendDiscoveryQueries,
+ @NonNull Collection<MdnsResponse> existingServices) {
this.taskArgs = taskArgs;
this.servicesToResolve.addAll(servicesToResolve);
this.subtypes.addAll(subtypes);
this.sendDiscoveryQueries = sendDiscoveryQueries;
+ this.existingServices.addAll(existingServices);
}
@Override
@@ -711,7 +741,6 @@
result =
new EnqueueMdnsQueryCallable(
socketClient,
- createMdnsPacketWriter(),
serviceType,
subtypes,
taskArgs.config.expectUnicastResponse,
@@ -721,7 +750,10 @@
sendDiscoveryQueries,
servicesToResolve,
clock,
- sharedLog)
+ sharedLog,
+ dependencies,
+ existingServices,
+ featureFlags.isQueryWithKnownAnswerEnabled())
.call();
} catch (RuntimeException e) {
sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
index c51811b..653ea6c 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
@@ -58,7 +58,6 @@
MdnsSocket(@NonNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider,
MulticastSocket multicastSocket, SharedLog sharedLog) throws IOException {
this.multicastNetworkInterfaceProvider = multicastNetworkInterfaceProvider;
- this.multicastNetworkInterfaceProvider.startWatchingConnectivityChanges();
this.multicastSocket = multicastSocket;
this.sharedLog = sharedLog;
// RFC Spec: https://tools.ietf.org/html/rfc6762
@@ -120,7 +119,6 @@
public void close() {
// This is a race with the use of the file descriptor (b/27403984).
multicastSocket.close();
- multicastNetworkInterfaceProvider.stopWatchingConnectivityChanges();
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 82c8c5b..9cfcba1 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -25,6 +25,7 @@
import android.net.wifi.WifiManager.MulticastLock;
import android.os.SystemClock;
import android.text.format.DateUtils;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.SharedLog;
@@ -106,6 +107,7 @@
@Nullable private Timer checkMulticastResponseTimer;
private final SharedLog sharedLog;
@NonNull private final MdnsFeatureFlags mdnsFeatureFlags;
+ private final MulticastNetworkInterfaceProvider interfaceProvider;
public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock,
SharedLog sharedLog, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
@@ -118,6 +120,7 @@
unicastReceiverBuffer = null;
}
this.mdnsFeatureFlags = mdnsFeatureFlags;
+ this.interfaceProvider = new MulticastNetworkInterfaceProvider(context, sharedLog);
}
@Override
@@ -138,6 +141,7 @@
cannotReceiveMulticastResponse.set(false);
shouldStopSocketLoop = false;
+ interfaceProvider.startWatchingConnectivityChanges();
try {
// TODO (changed when importing code): consider setting thread stats tag
multicastSocket = createMdnsSocket(MdnsConstants.MDNS_PORT, sharedLog);
@@ -183,6 +187,7 @@
}
multicastLock.release();
+ interfaceProvider.stopWatchingConnectivityChanges();
shouldStopSocketLoop = true;
waitForSendThreadToStop();
@@ -202,18 +207,18 @@
}
@Override
- public void sendPacketRequestingMulticastResponse(@NonNull DatagramPacket packet,
+ public void sendPacketRequestingMulticastResponse(@NonNull List<DatagramPacket> packets,
boolean onlyUseIpv6OnIpv6OnlyNetworks) {
- sendMdnsPacket(packet, multicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks);
+ sendMdnsPackets(packets, multicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks);
}
@Override
- public void sendPacketRequestingUnicastResponse(@NonNull DatagramPacket packet,
+ public void sendPacketRequestingUnicastResponse(@NonNull List<DatagramPacket> packets,
boolean onlyUseIpv6OnIpv6OnlyNetworks) {
if (useSeparateSocketForUnicast) {
- sendMdnsPacket(packet, unicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks);
+ sendMdnsPackets(packets, unicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks);
} else {
- sendMdnsPacket(packet, multicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks);
+ sendMdnsPackets(packets, multicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks);
}
}
@@ -234,17 +239,21 @@
return false;
}
- private void sendMdnsPacket(DatagramPacket packet, Queue<DatagramPacket> packetQueueToUse,
- boolean onlyUseIpv6OnIpv6OnlyNetworks) {
+ private void sendMdnsPackets(List<DatagramPacket> packets,
+ Queue<DatagramPacket> packetQueueToUse, boolean onlyUseIpv6OnIpv6OnlyNetworks) {
if (shouldStopSocketLoop && !MdnsConfigs.allowAddMdnsPacketAfterDiscoveryStops()) {
sharedLog.w("sendMdnsPacket() is called after discovery already stopped");
return;
}
+ if (packets.isEmpty()) {
+ Log.wtf(TAG, "No mDns packets to send");
+ return;
+ }
- final boolean isIpv4 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
- instanceof Inet4Address;
- final boolean isIpv6 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
- instanceof Inet6Address;
+ final boolean isIpv4 = ((InetSocketAddress) packets.get(0).getSocketAddress())
+ .getAddress() instanceof Inet4Address;
+ final boolean isIpv6 = ((InetSocketAddress) packets.get(0).getSocketAddress())
+ .getAddress() instanceof Inet6Address;
final boolean ipv6Only = multicastSocket != null && multicastSocket.isOnIPv6OnlyNetwork();
if (isIpv4 && ipv6Only) {
return;
@@ -254,10 +263,11 @@
}
synchronized (packetQueueToUse) {
- while (packetQueueToUse.size() >= MdnsConfigs.mdnsPacketQueueMaxSize()) {
+ while ((packetQueueToUse.size() + packets.size())
+ > MdnsConfigs.mdnsPacketQueueMaxSize()) {
packetQueueToUse.remove();
}
- packetQueueToUse.add(packet);
+ packetQueueToUse.addAll(packets);
}
triggerSendThread();
}
@@ -482,8 +492,7 @@
@VisibleForTesting
MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) throws IOException {
- return new MdnsSocket(new MulticastNetworkInterfaceProvider(context, sharedLog), port,
- sharedLog);
+ return new MdnsSocket(interfaceProvider, port, sharedLog);
}
private void sendPackets(List<DatagramPacket> packets, MdnsSocket socket) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
index b6000f0..b1a543a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
@@ -23,6 +23,7 @@
import java.io.IOException;
import java.net.DatagramPacket;
+import java.util.List;
/**
* Base class for multicast socket client.
@@ -40,15 +41,15 @@
void setCallback(@Nullable Callback callback);
/**
- * Send a mDNS request packet via given network that asks for multicast response.
+ * Send mDNS request packets via given network that asks for multicast response.
*/
- void sendPacketRequestingMulticastResponse(@NonNull DatagramPacket packet,
+ void sendPacketRequestingMulticastResponse(@NonNull List<DatagramPacket> packets,
boolean onlyUseIpv6OnIpv6OnlyNetworks);
/**
- * Send a mDNS request packet via given network that asks for unicast response.
+ * Send mDNS request packets via given network that asks for unicast response.
*/
- void sendPacketRequestingUnicastResponse(@NonNull DatagramPacket packet,
+ void sendPacketRequestingUnicastResponse(@NonNull List<DatagramPacket> packets,
boolean onlyUseIpv6OnIpv6OnlyNetworks);
/*** Notify that the given network is requested for mdns discovery / resolution */
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index d553210..3c11a24 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.mdns.util;
+import static com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
@@ -23,6 +25,7 @@
import android.os.Handler;
import android.os.SystemClock;
import android.util.ArraySet;
+import android.util.Pair;
import com.android.server.connectivity.mdns.MdnsConstants;
import com.android.server.connectivity.mdns.MdnsPacket;
@@ -30,13 +33,18 @@
import com.android.server.connectivity.mdns.MdnsRecord;
import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -226,6 +234,100 @@
}
/**
+ * Writes the possible query content of an MdnsPacket into the data buffer.
+ *
+ * <p>This method is specifically for query packets. It writes the question and answer sections
+ * into the data buffer only.
+ *
+ * @param packetCreationBuffer The data buffer for the query content.
+ * @param packet The MdnsPacket to be written into the data buffer.
+ * @return A Pair containing:
+ * 1. The remaining MdnsPacket data that could not fit in the buffer.
+ * 2. The length of the data written to the buffer.
+ */
+ @Nullable
+ private static Pair<MdnsPacket, Integer> writePossibleMdnsPacket(
+ @NonNull byte[] packetCreationBuffer, @NonNull MdnsPacket packet) throws IOException {
+ MdnsPacket remainingPacket;
+ final MdnsPacketWriter writer = new MdnsPacketWriter(packetCreationBuffer);
+ writer.writeUInt16(packet.transactionId); // Transaction ID
+
+ final int flagsPos = writer.getWritePosition();
+ writer.writeUInt16(0); // Flags, written later
+ writer.writeUInt16(0); // questions count, written later
+ writer.writeUInt16(0); // answers count, written later
+ writer.writeUInt16(0); // authority entries count, empty session for query
+ writer.writeUInt16(0); // additional records count, empty session for query
+
+ int writtenQuestions = 0;
+ int writtenAnswers = 0;
+ int lastValidPos = writer.getWritePosition();
+ try {
+ for (MdnsRecord record : packet.questions) {
+ // Questions do not have TTL or data
+ record.writeHeaderFields(writer);
+ writtenQuestions++;
+ lastValidPos = writer.getWritePosition();
+ }
+ for (MdnsRecord record : packet.answers) {
+ record.write(writer, 0L);
+ writtenAnswers++;
+ lastValidPos = writer.getWritePosition();
+ }
+ remainingPacket = null;
+ } catch (IOException e) {
+ // Went over the packet limit; truncate
+ if (writtenQuestions == 0 && writtenAnswers == 0) {
+ // No space to write even one record: just throw (as subclass of IOException)
+ throw e;
+ }
+
+ // Set the last valid position as the final position (not as a rewind)
+ writer.rewind(lastValidPos);
+ writer.clearRewind();
+
+ remainingPacket = new MdnsPacket(packet.flags,
+ packet.questions.subList(
+ writtenQuestions, packet.questions.size()),
+ packet.answers.subList(
+ writtenAnswers, packet.answers.size()),
+ Collections.emptyList(), /* authorityRecords */
+ Collections.emptyList() /* additionalRecords */);
+ }
+
+ final int len = writer.getWritePosition();
+ writer.rewind(flagsPos);
+ writer.writeUInt16(packet.flags | (remainingPacket == null ? 0 : FLAG_TRUNCATED));
+ writer.writeUInt16(writtenQuestions);
+ writer.writeUInt16(writtenAnswers);
+ writer.unrewind();
+
+ return Pair.create(remainingPacket, len);
+ }
+
+ /**
+ * Create Datagram packets from given MdnsPacket and InetSocketAddress.
+ *
+ * <p> If the MdnsPacket is too large for a single DatagramPacket, it will be split into
+ * multiple DatagramPackets.
+ */
+ public static List<DatagramPacket> createQueryDatagramPackets(
+ @NonNull byte[] packetCreationBuffer, @NonNull MdnsPacket packet,
+ @NonNull InetSocketAddress destination) throws IOException {
+ final List<DatagramPacket> datagramPackets = new ArrayList<>();
+ MdnsPacket remainingPacket = packet;
+ while (remainingPacket != null) {
+ final Pair<MdnsPacket, Integer> result =
+ writePossibleMdnsPacket(packetCreationBuffer, remainingPacket);
+ remainingPacket = result.first;
+ final int len = result.second;
+ final byte[] outBuffer = Arrays.copyOfRange(packetCreationBuffer, 0, len);
+ datagramPackets.add(new DatagramPacket(outBuffer, 0, outBuffer.length, destination));
+ }
+ return datagramPackets;
+ }
+
+ /**
* Checks if the MdnsRecord needs to be renewed or not.
*
* <p>As per RFC6762 7.1 no need to query if remaining TTL is more than half the original one,
diff --git a/service-t/src/com/android/server/ethernet/EthernetInterfaceStateMachine.java b/service-t/src/com/android/server/ethernet/EthernetInterfaceStateMachine.java
new file mode 100644
index 0000000..b8f6859
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetInterfaceStateMachine.java
@@ -0,0 +1,344 @@
+/*
+ * 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.ethernet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.ip.IIpClient;
+import android.net.ip.IpClientCallbacks;
+import android.net.ip.IpClientManager;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.util.State;
+import com.android.net.module.util.SyncStateMachine;
+import com.android.net.module.util.SyncStateMachine.StateInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * EthernetInterfaceStateMachine manages the lifecycle of an ethernet-like network interface which
+ * includes managing a NetworkOffer, IpClient, and NetworkAgent as well as making the interface
+ * available as a tethering downstream.
+ *
+ * All methods exposed by this class *must* be called on the Handler thread provided in the
+ * constructor.
+ */
+class EthernetInterfaceStateMachine extends SyncStateMachine {
+ private static final String TAG = EthernetInterfaceStateMachine.class.getSimpleName();
+
+ private static final int CMD_ON_LINK_UP = 1;
+ private static final int CMD_ON_LINK_DOWN = 2;
+ private static final int CMD_ON_NETWORK_NEEDED = 3;
+ private static final int CMD_ON_NETWORK_UNNEEDED = 4;
+ private static final int CMD_ON_IPCLIENT_CREATED = 5;
+
+ private class EthernetNetworkOfferCallback implements NetworkOfferCallback {
+ private final Set<Integer> mRequestIds = new ArraySet<>();
+
+ @Override
+ public void onNetworkNeeded(@NonNull NetworkRequest request) {
+ if (this != mNetworkOfferCallback) {
+ return;
+ }
+
+ mRequestIds.add(request.requestId);
+ if (mRequestIds.size() == 1) {
+ processMessage(CMD_ON_NETWORK_NEEDED);
+ }
+ }
+
+ @Override
+ public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+ if (this != mNetworkOfferCallback) {
+ return;
+ }
+
+ if (!mRequestIds.remove(request.requestId)) {
+ // This can only happen if onNetworkNeeded was not called for a request or if
+ // the requestId changed. Both should *never* happen.
+ Log.wtf(TAG, "onNetworkUnneeded called for unknown request");
+ }
+ if (mRequestIds.isEmpty()) {
+ processMessage(CMD_ON_NETWORK_UNNEEDED);
+ }
+ }
+ }
+
+ private class EthernetIpClientCallback extends IpClientCallbacks {
+ private final ConditionVariable mOnQuitCv = new ConditionVariable(false);
+
+ private void safelyPostOnHandler(Runnable r) {
+ mHandler.post(() -> {
+ if (this != mIpClientCallback) {
+ return;
+ }
+ r.run();
+ });
+ }
+
+ @Override
+ public void onIpClientCreated(IIpClient ipClient) {
+ safelyPostOnHandler(() -> {
+ // TODO: add a SyncStateMachine#processMessage(cmd, obj) overload.
+ processMessage(CMD_ON_IPCLIENT_CREATED, 0, 0,
+ mDependencies.makeIpClientManager(ipClient));
+ });
+ }
+
+ public void waitOnQuit() {
+ if (!mOnQuitCv.block(5_000 /* timeoutMs */)) {
+ Log.wtf(TAG, "Timed out waiting on IpClient to shutdown.");
+ }
+ }
+
+ @Override
+ public void onQuit() {
+ mOnQuitCv.open();
+ }
+ }
+
+ private @Nullable EthernetNetworkOfferCallback mNetworkOfferCallback;
+ private @Nullable EthernetIpClientCallback mIpClientCallback;
+ private @Nullable IpClientManager mIpClient;
+ private final String mIface;
+ private final Handler mHandler;
+ private final Context mContext;
+ private final NetworkCapabilities mCapabilities;
+ private final NetworkProvider mNetworkProvider;
+ private final EthernetNetworkFactory.Dependencies mDependencies;
+ private boolean mLinkUp = false;
+
+ /** Interface is in tethering mode. */
+ private class TetheringState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_LINK_UP:
+ case CMD_ON_LINK_DOWN:
+ // TODO: think about what to do here.
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /** Link is down */
+ private class LinkDownState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_LINK_UP:
+ transitionTo(mStoppedState);
+ return HANDLED;
+ case CMD_ON_LINK_DOWN:
+ // do nothing, already in the correct state.
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /** Parent states of all states that do not cause a NetworkOffer to be extended. */
+ private class NetworkOfferExtendedState extends State {
+ @Override
+ public void enter() {
+ if (mNetworkOfferCallback != null) {
+ // This should never happen. If it happens anyway, log and move on.
+ Log.wtf(TAG, "Previous NetworkOffer was never retracted");
+ }
+
+ mNetworkOfferCallback = new EthernetNetworkOfferCallback();
+ final NetworkScore defaultScore = new NetworkScore.Builder().build();
+ mNetworkProvider.registerNetworkOffer(defaultScore,
+ new NetworkCapabilities(mCapabilities), cmd -> mHandler.post(cmd),
+ mNetworkOfferCallback);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_LINK_UP:
+ // do nothing, already in the correct state.
+ return HANDLED;
+ case CMD_ON_LINK_DOWN:
+ transitionTo(mLinkDownState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mNetworkProvider.unregisterNetworkOffer(mNetworkOfferCallback);
+ mNetworkOfferCallback = null;
+ }
+ }
+
+ /**
+ * Offer is extended but has not been requested.
+ *
+ * StoppedState's sole purpose is to react to a CMD_ON_NETWORK_NEEDED and transition to
+ * StartedState when that happens. Note that StoppedState could be rolled into
+ * NetworkOfferExtendedState. However, keeping the states separate provides some additional
+ * protection by logging a Log.wtf if a CMD_ON_NETWORK_NEEDED is received in an unexpected state
+ * (i.e. StartedState or RunningState). StoppedState is a child of NetworkOfferExtendedState.
+ */
+ private class StoppedState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_NETWORK_NEEDED:
+ transitionTo(mStartedState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /** Network is needed, starts IpClient and manages its lifecycle */
+ private class StartedState extends State {
+ @Override
+ public void enter() {
+ mIpClientCallback = new EthernetIpClientCallback();
+ mDependencies.makeIpClient(mContext, mIface, mIpClientCallback);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_NETWORK_UNNEEDED:
+ transitionTo(mStoppedState);
+ return HANDLED;
+ case CMD_ON_IPCLIENT_CREATED:
+ mIpClient = (IpClientManager) msg.obj;
+ transitionTo(mRunningState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ if (mIpClient != null) {
+ mIpClient.shutdown();
+ // TODO: consider adding a StoppingState and making the shutdown operation
+ // asynchronous.
+ mIpClientCallback.waitOnQuit();
+ }
+ mIpClientCallback = null;
+ }
+ }
+
+ /** IpClient is running, starts provisioning and registers NetworkAgent */
+ private class RunningState extends State {
+
+ }
+
+ private final TetheringState mTetheringState = new TetheringState();
+ private final LinkDownState mLinkDownState = new LinkDownState();
+ private final NetworkOfferExtendedState mOfferExtendedState = new NetworkOfferExtendedState();
+ private final StoppedState mStoppedState = new StoppedState();
+ private final StartedState mStartedState = new StartedState();
+ private final RunningState mRunningState = new RunningState();
+
+ public EthernetInterfaceStateMachine(String iface, Handler handler, Context context,
+ NetworkCapabilities capabilities, NetworkProvider provider,
+ EthernetNetworkFactory.Dependencies deps) {
+ super(TAG + "." + iface, handler.getLooper().getThread());
+
+ mIface = iface;
+ mHandler = handler;
+ mContext = context;
+ mCapabilities = capabilities;
+ mNetworkProvider = provider;
+ mDependencies = deps;
+
+ // Interface lifecycle:
+ // [ LinkDownState ]
+ // |
+ // v
+ // *link comes up*
+ // |
+ // v
+ // [ StoppedState ]
+ // |
+ // v
+ // *network is needed*
+ // |
+ // v
+ // [ StartedState ]
+ // |
+ // v
+ // *IpClient is created*
+ // |
+ // v
+ // [ RunningState ]
+ // |
+ // v
+ // *interface is requested for tethering*
+ // |
+ // v
+ // [TetheringState]
+ //
+ // Tethering mode is special as the interface is configured by Tethering, rather than the
+ // ethernet module.
+ final List<StateInfo> states = new ArrayList<>();
+ states.add(new StateInfo(mTetheringState, null));
+
+ // CHECKSTYLE:OFF IndentationCheck
+ // Initial state
+ states.add(new StateInfo(mLinkDownState, null));
+ states.add(new StateInfo(mOfferExtendedState, null));
+ states.add(new StateInfo(mStoppedState, mOfferExtendedState));
+ states.add(new StateInfo(mStartedState, mOfferExtendedState));
+ states.add(new StateInfo(mRunningState, mStartedState));
+ // CHECKSTYLE:ON IndentationCheck
+ addAllStates(states);
+
+ // TODO: set initial state to TetheringState if a tethering interface has been requested and
+ // this is the first interface to be added.
+ start(mLinkDownState);
+ }
+
+ public boolean updateLinkState(boolean up) {
+ if (mLinkUp == up) {
+ return false;
+ }
+
+ // TODO: consider setting mLinkUp as part of processMessage().
+ mLinkUp = up;
+ if (!up) { // was up, goes down
+ processMessage(CMD_ON_LINK_DOWN);
+ } else { // was down, comes up
+ processMessage(CMD_ON_LINK_UP);
+ }
+
+ return true;
+ }
+}
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 458d64f..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;
@@ -382,10 +386,9 @@
});
}
- @VisibleForTesting(visibility = PACKAGE)
- protected void setInterfaceEnabled(@NonNull final String iface, boolean enabled,
- @Nullable final EthernetCallback cb) {
- mHandler.post(() -> updateInterfaceState(iface, enabled, cb));
+ /** Configure the administrative state of ethernet interface by toggling IFF_UP. */
+ public void setInterfaceEnabled(String iface, boolean enabled, EthernetCallback cb) {
+ mHandler.post(() -> setInterfaceAdministrativeState(iface, enabled, cb));
}
IpConfiguration getIpConfiguration(String iface) {
@@ -461,7 +464,7 @@
if (!include) {
removeTestData();
}
- mHandler.post(() -> trackAvailableInterfaces());
+ trackAvailableInterfaces();
});
}
@@ -583,6 +586,7 @@
removeInterface(iface);
if (iface.equals(mTetheringInterface)) {
mTetheringInterface = null;
+ mTetheringInterfaceHwAddr = null;
}
broadcastInterfaceStateChange(iface);
}
@@ -611,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
@@ -643,25 +648,40 @@
}
}
- private void updateInterfaceState(String iface, boolean up) {
- updateInterfaceState(iface, up, new EthernetCallback(null /* cb */));
- }
-
- // TODO(b/225315248): enable/disableInterface() should not affect link state.
- private void updateInterfaceState(String iface, boolean up, EthernetCallback cb) {
- final int mode = getInterfaceMode(iface);
- if (mode == INTERFACE_MODE_SERVER || !mFactory.hasInterface(iface)) {
- // The interface is in server mode or is not tracked.
- cb.onError("Failed to set link state " + (up ? "up" : "down") + " for " + iface);
+ private void setInterfaceAdministrativeState(String iface, boolean up, EthernetCallback cb) {
+ if (getInterfaceState(iface) == EthernetManager.STATE_ABSENT) {
+ cb.onError("Failed to enable/disable absent interface: " + iface);
+ return;
+ }
+ if (getInterfaceRole(iface) == EthernetManager.ROLE_SERVER) {
+ // TODO: support setEthernetState for server mode interfaces.
+ cb.onError("Failed to enable/disable interface in server mode: " + iface);
return;
}
+ if (up) {
+ // WARNING! setInterfaceUp() clears the IPv4 address and readds it. Calling
+ // enableInterface() on an active interface can lead to a provisioning failure which
+ // will cause IpClient to be restarted.
+ // TODO: use netlink directly rather than calling into netd.
+ NetdUtils.setInterfaceUp(mNetd, iface);
+ } else {
+ NetdUtils.setInterfaceDown(mNetd, iface);
+ }
+ cb.onResult(iface);
+ }
+
+ private void updateInterfaceState(String iface, boolean up) {
+ final int mode = getInterfaceMode(iface);
+ if (mode == INTERFACE_MODE_SERVER) {
+ // TODO: support tracking link state for interfaces in server mode.
+ return;
+ }
+
+ // If updateInterfaceLinkState returns false, the interface is already in the correct state.
if (mFactory.updateInterfaceLinkState(iface, up)) {
broadcastInterfaceStateChange(iface);
}
- // If updateInterfaceLinkState returns false, the interface is already in the correct state.
- // Always return success.
- cb.onResult(iface);
}
private void maybeUpdateServerModeInterfaceState(String iface, boolean available) {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index f7f133c..114cf2e 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);
+ }
}
/**
@@ -1783,6 +1897,8 @@
if (transport == TRANSPORT_WIFI) {
ifaceSet = mAllWifiIfacesSinceBoot;
} else if (transport == TRANSPORT_CELLULAR) {
+ // Since satellite networks appear under type mobile, this includes both cellular
+ // and satellite active interfaces
ifaceSet = mAllMobileIfacesSinceBoot;
} else {
throw new IllegalArgumentException("Invalid transport " + transport);
@@ -1986,53 +2102,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) {
@@ -2193,7 +2364,9 @@
for (NetworkStateSnapshot snapshot : snapshots) {
final int displayTransport =
getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
- final boolean isMobile = (NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport);
+ // Consider satellite transport to support satellite stats appear as type_mobile
+ final boolean isMobile = NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport
+ || NetworkCapabilities.TRANSPORT_SATELLITE == displayTransport;
final boolean isWifi = (NetworkCapabilities.TRANSPORT_WIFI == displayTransport);
final boolean isDefault = CollectionUtils.contains(
mDefaultNetworks, snapshot.getNetwork());
@@ -2326,12 +2499,14 @@
}
/**
- * For networks with {@code TRANSPORT_CELLULAR}, get ratType that was obtained through
- * {@link PhoneStateListener}. Otherwise, return 0 given that other networks with different
- * transport types do not actually fill this value.
+ * For networks with {@code TRANSPORT_CELLULAR} Or {@code TRANSPORT_SATELLITE}, get ratType
+ * that was obtained through {@link PhoneStateListener}. Otherwise, return 0 given that other
+ * networks with different transport types do not actually fill this value.
*/
private int getRatTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) {
- if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && !state.getNetworkCapabilities()
+ .hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)) {
return 0;
}
@@ -2784,6 +2959,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-t/src/com/android/server/net/TrafficStatsRateLimitCache.java b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
index 8598ac4..ca97d07 100644
--- a/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
+++ b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
@@ -19,12 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkStats;
+import android.util.LruCache;
import com.android.internal.annotations.GuardedBy;
import java.time.Clock;
-import java.util.HashMap;
import java.util.Objects;
+import java.util.function.Supplier;
/**
* A thread-safe cache for storing and retrieving {@link NetworkStats.Entry} objects,
@@ -39,10 +40,12 @@
*
* @param clock The {@link Clock} to use for determining timestamps.
* @param expiryDurationMs The expiry duration in milliseconds.
+ * @param maxSize Maximum number of entries.
*/
- TrafficStatsRateLimitCache(@NonNull Clock clock, long expiryDurationMs) {
+ TrafficStatsRateLimitCache(@NonNull Clock clock, long expiryDurationMs, int maxSize) {
mClock = clock;
mExpiryDurationMs = expiryDurationMs;
+ mMap = new LruCache<>(maxSize);
}
private static class TrafficStatsCacheKey {
@@ -81,7 +84,7 @@
}
@GuardedBy("mMap")
- private final HashMap<TrafficStatsCacheKey, TrafficStatsCacheValue> mMap = new HashMap<>();
+ private final LruCache<TrafficStatsCacheKey, TrafficStatsCacheValue> mMap;
/**
* Retrieves a {@link NetworkStats.Entry} from the cache, associated with the given key.
@@ -105,6 +108,36 @@
}
/**
+ * Retrieves a {@link NetworkStats.Entry} from the cache, associated with the given key.
+ * If the entry is not found in the cache or has expired, computes it using the provided
+ * {@code supplier} and stores the result in the cache.
+ *
+ * @param iface The interface name to include in the cache key. {@code IFACE_ALL}
+ * if not applicable.
+ * @param uid The UID to include in the cache key. {@code UID_ALL} if not applicable.
+ * @param supplier The {@link Supplier} to compute the {@link NetworkStats.Entry} if not found.
+ * @return The cached or computed {@link NetworkStats.Entry}, or null if not found, expired,
+ * or if the {@code supplier} returns null.
+ */
+ @Nullable
+ NetworkStats.Entry getOrCompute(String iface, int uid,
+ @NonNull Supplier<NetworkStats.Entry> supplier) {
+ synchronized (mMap) {
+ final NetworkStats.Entry cachedValue = get(iface, uid);
+ if (cachedValue != null) {
+ return cachedValue;
+ }
+
+ // Entry not found or expired, compute it
+ final NetworkStats.Entry computedEntry = supplier.get();
+ if (computedEntry != null && !computedEntry.isEmpty()) {
+ put(iface, uid, computedEntry);
+ }
+ return computedEntry;
+ }
+ }
+
+ /**
* Stores a {@link NetworkStats.Entry} in the cache, associated with the given key.
*
* @param iface The interface name to include in the cache key. Null if not applicable.
@@ -124,7 +157,7 @@
*/
void clear() {
synchronized (mMap) {
- mMap.clear();
+ mMap.evictAll();
}
}
diff --git a/service/Android.bp b/service/Android.bp
index c35c4f8..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",
@@ -199,7 +199,9 @@
"PlatformProperties",
"service-connectivity-protos",
"service-connectivity-stats-protos",
- "net-utils-multicast-forwarding-structs",
+ // The required dependency net-utils-device-common-struct-base is in the classpath via
+ // framework-connectivity
+ "net-utils-device-common-struct",
],
apex_available: [
"com.android.tethering",
diff --git a/service/ServiceConnectivityResources/res/values-fa/strings.xml b/service/ServiceConnectivityResources/res/values-fa/strings.xml
index 09f1255..02c60df 100644
--- a/service/ServiceConnectivityResources/res/values-fa/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fa/strings.xml
@@ -23,15 +23,15 @@
<!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
<string name="mobile_network_available_no_internet" msgid="1000871587359324217">"اتصال اینترنت وجود ندارد"</string>
- <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ممکن است داده <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> تمام شده باشد. برای گزینهها تکضرب بزنید."</string>
- <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ممکن است داده شما تمام شده باشد. برای گزینهها تکضرب بزنید."</string>
+ <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ممکن است داده <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> تمام شده باشد. برای گزینهها ضربه بزنید."</string>
+ <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ممکن است داده شما تمام شده باشد. برای گزینهها ضربه بزنید."</string>
<string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> به اینترنت دسترسی ندارد"</string>
- <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"برای گزینهها تکضرب بزنید"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"برای گزینهها ضربه بزنید"</string>
<string name="mobile_no_internet" msgid="4087718456753201450">"شبکه تلفن همراه به اینترنت دسترسی ندارد"</string>
<string name="other_networks_no_internet" msgid="5693932964749676542">"شبکه به اینترنت دسترسی ندارد"</string>
<string name="private_dns_broken_detailed" msgid="2677123850463207823">"سرور DNS خصوصی قابل دسترسی نیست"</string>
<string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> اتصال محدودی دارد"</string>
- <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"بههرصورت، برای اتصال تکضرب بزنید"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"بههرصورت، برای اتصال ضربه بزنید"</string>
<string name="network_switch_metered" msgid="5016937523571166319">"به <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> تغییر کرد"</string>
<string name="network_switch_metered_detail" msgid="1257300152739542096">"وقتی <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> به اینترنت دسترسی نداشته باشد، دستگاه از <xliff:g id="NEW_NETWORK">%1$s</xliff:g> استفاده میکند. ممکن است هزینههایی اعمال شود."</string>
<string name="network_switch_metered_toast" msgid="70691146054130335">"از <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> به <xliff:g id="NEW_NETWORK">%2$s</xliff:g> تغییر کرد"</string>
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index f7e47f5..02a9ce6 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -31,4 +31,32 @@
Thread Network regulatory purposes.
-->
<bool name="config_thread_location_use_for_country_code_enabled">true</bool>
+
+ <!-- Specifies the UTF-8 vendor name of this device. If this value is not an empty string, it
+ will be included in TXT value (key is 'vn') of the "_meshcop._udp" mDNS service which is
+ published by the Thread service. A non-empty string value must not exceed length of 24 UTF-8
+ bytes.
+ -->
+ <string translatable="false" name="config_thread_vendor_name">Android</string>
+
+ <!-- Specifies the 24 bits vendor OUI of this device. If this value is not an empty string, it
+ will be included in TXT (key is 'vo') value of the "_meshcop._udp" mDNS service which is
+ published by the Thread service. The OUI can be represented as a base-16 number of six
+ hexadecimal digits, or octets separated by hyphens or dots. For example, "ACDE48", "AC-DE-48"
+ and "AC:DE:48" are all valid representations of the same OUI value.
+ -->
+ <string translatable="false" name="config_thread_vendor_oui"></string>
+
+ <!-- Specifies the UTF-8 product model name of this device. If this value is not an empty
+ string, it will be included in TXT (key is 'mn') value of the "_meshcop._udp" mDNS service
+ which is published by the Thread service. A non-empty string value must not exceed length of 24
+ UTF-8 bytes.
+ -->
+ <string translatable="false" name="config_thread_model_name">Thread Border Router</string>
+
+ <!-- Whether the Thread network will be managed by the Google Home ecosystem. When this value
+ is set, a TXT entry "vgh=0" or "vgh=1" will be added to the "_mehscop._udp" mDNS service
+ respectively (The TXT value is a string).
+ -->
+ <bool name="config_thread_managed_by_google_home">false</bool>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index d9af5a3..158b0c8 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -48,6 +48,9 @@
<!-- Configuration values for ThreadNetworkService -->
<item type="bool" name="config_thread_default_enabled" />
<item type="bool" name="config_thread_location_use_for_country_code_enabled" />
+ <item type="string" name="config_thread_vendor_name" />
+ <item type="string" name="config_thread_vendor_oui" />
+ <item type="string" name="config_thread_model_name" />
</policy>
</overlayable>
</resources>
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index c125bd6..c0082bb 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -80,7 +80,8 @@
case VERIFY_BIN: return;
case VERIFY_PROG: fd = bpf::retrieveProgram(path); break;
case VERIFY_MAP_RO: fd = bpf::mapRetrieveRO(path); break;
- case VERIFY_MAP_RW: fd = bpf::mapRetrieveRW(path); break;
+ // lockless: we're just checking access rights and will immediately close the fd
+ case VERIFY_MAP_RW: fd = bpf::mapRetrieveLocklessRW(path); break;
}
if (fd < 0) ALOGF("bpf_obj_get '%s' failed, errno=%d", path, errno);
@@ -113,7 +114,8 @@
if (!modules::sdklevel::IsAtLeastT()) return;
V("/sys/fs/bpf", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf", DIR);
- V("/sys/fs/bpf/net_shared", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
+
+ V("/sys/fs/bpf/net_shared", S_IFDIR|01777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
// pre-U we do not have selinux privs to getattr on bpf maps/progs
// so while the below *should* be as listed, we have no way to actually verify
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index 3e11d52..b09589c 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -3,17 +3,6 @@
<issue
id="NewApi"
- message="Call requires API level 33 (current min is 30): `getUidRule`"
- errorLine1=" return BpfNetMapsReader.getUidRule(sUidOwnerMap, childChain, uid);"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java"
- line="643"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
message="Call requires API level 31 (current min is 30): `BpfBitmap`"
errorLine1=" return new BpfBitmap(BLOCKED_PORTS_MAP_PATH);"
errorLine2=" ~~~~~~~~~~~~~">
diff --git a/service/src/com/android/server/BpfLoaderRcUtils.java b/service/src/com/android/server/BpfLoaderRcUtils.java
deleted file mode 100644
index 293e757..0000000
--- a/service/src/com/android/server/BpfLoaderRcUtils.java
+++ /dev/null
@@ -1,161 +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 com.android.server;
-
-import android.annotation.NonNull;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.build.SdkLevel;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * BpfRcUtils is responsible for comparing the bpf loader rc file.
- *
- * {@hide}
- */
-public class BpfLoaderRcUtils {
- public static final String TAG = BpfLoaderRcUtils.class.getSimpleName();
-
- private static final List<String> BPF_LOADER_RC_S_T = List.of(
- "service bpfloader /system/bin/bpfloader",
- "capabilities CHOWN SYS_ADMIN NET_ADMIN",
- "rlimit memlock 1073741824 1073741824",
- "oneshot",
- "reboot_on_failure reboot,bpfloader-failed",
- "updatable"
- );
-
- private static final List<String> BPF_LOADER_RC_U = List.of(
- "service bpfloader /system/bin/bpfloader",
- "capabilities CHOWN SYS_ADMIN NET_ADMIN",
- "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
- "user root",
- "rlimit memlock 1073741824 1073741824",
- "oneshot",
- "reboot_on_failure reboot,bpfloader-failed",
- "updatable"
- );
-
- private static final List<String> BPF_LOADER_RC_UQPR2 = List.of(
- "service bpfloader /system/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",
- "rlimit memlock 1073741824 1073741824",
- "oneshot",
- "reboot_on_failure reboot,bpfloader-failed",
- "updatable"
- );
-
-
- private static final String BPF_LOADER_RC_FILE_PATH = "/etc/init/bpfloader.rc";
- private static final String NET_BPF_LOAD_RC_FILE_PATH = "/etc/init/netbpfload.rc";
-
- private BpfLoaderRcUtils() {
- }
-
- /**
- * Load the bpf rc file content from the input stream.
- */
- @VisibleForTesting
- public static List<String> loadExistingBpfRcFile(@NonNull InputStream inputStream) {
- List<String> contents = new ArrayList<>();
- boolean bpfSectionFound = false;
- try (BufferedReader br = new BufferedReader(
- new InputStreamReader(inputStream, StandardCharsets.ISO_8859_1))) {
- String line;
- while ((line = br.readLine()) != null) {
- line = line.trim();
- if (line.isEmpty()) {
- continue;
- }
- if (line.startsWith("#")) {
- continue;
- }
- // If bpf service section was found and new service or action section start. The
- // read should stop.
- if (bpfSectionFound && (line.startsWith("service ") || (line.startsWith("on ")))) {
- break;
- }
- if (line.startsWith("service bpfloader ")) {
- bpfSectionFound = true;
- }
- if (bpfSectionFound) {
- contents.add(line);
- }
- }
- } catch (IOException e) {
- Log.wtf("read input stream failed.", e);
- contents.clear();
- return contents;
- }
- return contents;
- }
-
- /**
- * Check the bpfLoader rc file on the system image matches any of the template files.
- */
- public static boolean checkBpfLoaderRc() {
- File bpfRcFile = new File(BPF_LOADER_RC_FILE_PATH);
- if (!bpfRcFile.exists()) {
- if (SdkLevel.isAtLeastU()) {
- bpfRcFile = new File(NET_BPF_LOAD_RC_FILE_PATH);
- }
- if (!bpfRcFile.exists()) {
- Log.wtf(TAG,
- "neither " + BPF_LOADER_RC_FILE_PATH + " nor " + NET_BPF_LOAD_RC_FILE_PATH
- + " exist.");
- return false;
- }
- // Check bpf rc file in U QPR2
- return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_UQPR2);
- }
-
- if (SdkLevel.isAtLeastU()) {
- // Check bpf rc file in U
- return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_U);
- }
- // Check bpf rc file in S/T
- return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_S_T);
- }
-
- private static boolean compareBpfLoaderRc(@NonNull File bpfRcFile,
- @NonNull List<String> template) {
- try {
- List<String> actualContent = loadExistingBpfRcFile(new FileInputStream(bpfRcFile));
- if (!actualContent.equals(template)) {
- Log.wtf(TAG, "BPF rc file is not same as the template files " + actualContent);
- return false;
- }
- } catch (FileNotFoundException e) {
- Log.wtf(bpfRcFile.getPath() + " doesn't exist.", e);
- return false;
- }
- return true;
- }
-}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index a7fddd0..44868b2d 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;
@@ -49,12 +49,13 @@
import android.app.StatsManager;
import android.content.Context;
-import android.net.BpfNetMapsReader;
+import android.net.BpfNetMapsUtils;
import android.net.INetd;
import android.net.UidOwnerValue;
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 SingleWriterBpfMap.getSingleton(
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 SingleWriterBpfMap.getSingleton(
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 SingleWriterBpfMap.getSingleton(
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 SingleWriterBpfMap.getSingleton(
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 SingleWriterBpfMap.getSingleton(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
@@ -535,14 +482,11 @@
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
- *
- * @deprecated Use {@link BpfNetMapsReader#isChainEnabled} instead.
*/
- // TODO: Migrate the callers to use {@link BpfNetMapsReader#isChainEnabled} instead.
@Deprecated
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public boolean isChainEnabled(final int childChain) {
- return BpfNetMapsReader.isChainEnabled(sConfigurationMap, childChain);
+ return BpfNetMapsUtils.isChainEnabled(sConfigurationMap, childChain);
}
private Set<Integer> asSet(final int[] uids) {
@@ -635,14 +579,13 @@
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
- *
- * @deprecated use {@link BpfNetMapsReader#getUidRule} instead.
*/
- // TODO: Migrate the callers to use {@link BpfNetMapsReader#getUidRule} instead.
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public int getUidRule(final int childChain, final int uid) {
- return BpfNetMapsReader.getUidRule(sUidOwnerMap, childChain, uid);
+ 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<>();
@@ -671,6 +614,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)) {
@@ -692,6 +636,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)) {
@@ -865,6 +810,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.
@@ -924,6 +891,49 @@
}
}
+ /**
+ * 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.
+ *
+ * @param uid The target uid.
+ * @param isNetworkMetered Whether the target network is metered.
+ *
+ * @return True if the network is blocked. Otherwise, false.
+ * @throws ServiceSpecificException if the read fails.
+ *
+ * @hide
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered) {
+ return BpfNetMapsUtils.isUidNetworkingBlocked(uid, isNetworkMetered,
+ sConfigurationMap, sUidOwnerMap, sDataSaverEnabledMap);
+ }
+
/** Register callback for statsd to pull atom. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setPullAtomCallback(final Context context) {
@@ -986,6 +996,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 6839c22..2a3058c 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,14 +36,17 @@
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;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
+import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_NONE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -61,7 +66,9 @@
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL;
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;
@@ -72,6 +79,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -97,29 +105,44 @@
import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST;
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;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_GETSOCKOPT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_CONNECT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_CONNECT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_EGRESS;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_INGRESS;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_CREATE;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_RELEASE;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_SETSOCKOPT;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP4_RECVMSG;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP4_SENDMSG;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_RECVMSG;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_SENDMSG;
import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
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;
import android.Manifest;
import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresApi;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@@ -214,7 +237,6 @@
import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnTransportInfo;
-import android.net.connectivity.ConnectivityCompatChanges;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netd.aidl.NativeUidRangeConfig;
@@ -269,8 +291,6 @@
import android.util.SparseIntArray;
import android.util.StatsEvent;
-import androidx.annotation.RequiresApi;
-
import com.android.connectivity.resources.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -392,6 +412,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);
@@ -474,10 +496,35 @@
private volatile boolean mLockdownEnabled;
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();
@@ -545,6 +592,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.
@@ -779,11 +828,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
@@ -844,9 +892,16 @@
private static final int EVENT_UID_FROZEN_STATE_CHANGED = 61;
/**
- * Event to inform the ConnectivityService handler when a uid has lost carrier privileges.
+ * Event to update firewall socket destroy reasons for uids.
+ * obj = List of Pair(uid, socketDestroyReasons)
*/
- private static final int EVENT_UID_CARRIER_PRIVILEGES_LOST = 62;
+ 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
@@ -992,14 +1047,22 @@
// 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;
/**
* Implements support for the legacy "one network per network type" model.
@@ -1288,14 +1351,6 @@
}
private final LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(this);
- @VisibleForTesting
- void onCarrierPrivilegesLost(Integer uid, Integer subId) {
- if (mRequestRestrictedWifiEnabled) {
- mHandler.sendMessage(mHandler.obtainMessage(
- EVENT_UID_CARRIER_PRIVILEGES_LOST, uid, subId));
- }
- }
-
final LocalPriorityDump mPriorityDumper = new LocalPriorityDump();
/**
* Helper class which parses out priority arguments and dumps sections according to their
@@ -1517,10 +1572,11 @@
@NonNull final Context context,
@NonNull final TelephonyManager tm,
boolean requestRestrictedWifiEnabled,
- @NonNull BiConsumer<Integer, Integer> listener) {
+ @NonNull BiConsumer<Integer, Integer> listener,
+ @NonNull final Handler connectivityServiceHandler) {
if (isAtLeastT()) {
- return new CarrierPrivilegeAuthenticator(
- context, tm, requestRestrictedWifiEnabled, listener);
+ return new CarrierPrivilegeAuthenticator(context, tm, requestRestrictedWifiEnabled,
+ listener, connectivityServiceHandler);
} else {
return null;
}
@@ -1732,7 +1788,7 @@
mDefaultRequest = new NetworkRequestInfo(
Process.myUid(), defaultInternetRequest, null,
null /* binder */, NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
- null /* attributionTags */);
+ null /* attributionTags */, DECLARED_METHODS_NONE);
mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
mDefaultNetworkRequests.add(mDefaultRequest);
mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
@@ -1803,9 +1859,13 @@
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
mRequestRestrictedWifiEnabled = mDeps.isAtLeastU()
&& 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::onCarrierPrivilegesLost);
+ this::handleUidCarrierPrivilegesLost, mHandler);
if (mDeps.isAtLeastU()
&& mDeps
@@ -1819,7 +1879,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);
@@ -1950,8 +2015,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) {
@@ -1978,10 +2042,8 @@
activityManager.registerUidFrozenStateChangedCallback(
(Runnable r) -> r.run(), frozenStateChangedCallback);
}
-
- if (mDeps.isFeatureNotChickenedOut(mContext, LOG_BPF_RC)) {
- mHandler.post(BpfLoaderRcUtils::checkBpfLoaderRc);
- }
+ mIngressToVpnAddressFiltering = mDeps.isAtLeastT()
+ && mDeps.isFeatureNotChickenedOut(mContext, INGRESS_TO_VPN_ADDRESS_FILTERING);
}
/**
@@ -2113,7 +2175,7 @@
handleRegisterNetworkRequest(new NetworkRequestInfo(
Process.myUid(), networkRequest, null /* messenger */, null /* binder */,
NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
- null /* attributionTags */));
+ null /* attributionTags */, DECLARED_METHODS_NONE));
} else {
handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
/* callOnUnavailable */ false);
@@ -2229,6 +2291,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.
*/
@@ -2242,7 +2309,17 @@
final long ident = Binder.clearCallingIdentity();
try {
final boolean metered = nc == null ? true : nc.isMetered();
- return mPolicyManager.isUidNetworkingBlocked(uid, metered);
+ if (mDeps.isAtLeastV()) {
+ 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);
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2320,7 +2397,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;
}
@@ -2611,7 +2688,7 @@
// Not the system, so it's an app requesting on its own behalf.
type = RequestType.RT_APP.getNumber();
}
- countPerType.put(type, countPerType.get(type, 0));
+ countPerType.put(type, countPerType.get(type, 0) + 1);
}
for (int i = countPerType.size() - 1; i >= 0; --i) {
final RequestCountForType.Builder r = RequestCountForType.newBuilder();
@@ -2769,6 +2846,7 @@
private boolean canSeeAllowedUids(final int pid, final int uid, final int netOwnerUid) {
return Process.SYSTEM_UID == uid
+ || netOwnerUid == uid
|| hasAnyPermissionOf(mContext, pid, uid,
android.Manifest.permission.NETWORK_FACTORY);
}
@@ -2796,7 +2874,6 @@
}
if (!canSeeAllowedUids(callerPid, callerUid, newNc.getOwnerUid())) {
newNc.setAllowedUids(new ArraySet<>());
- newNc.setSubscriptionIds(Collections.emptySet());
}
redactUnderlyingNetworksForCapabilities(newNc, callerPid, callerUid);
@@ -3024,6 +3101,23 @@
}
}
+ private void maybeDisableLocalNetworkMatching(NetworkCapabilities nc, int callingUid) {
+ if (mDeps.isChangeEnabled(ENABLE_MATCH_LOCAL_NETWORK, callingUid)) {
+ return;
+ }
+ // If NET_CAPABILITY_LOCAL_NETWORK is not added to capability, request should not be
+ // satisfied by local networks.
+ if (!nc.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)) {
+ nc.addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK);
+ }
+ }
+
+ private void restrictRequestNetworkCapabilitiesForCaller(NetworkCapabilities nc,
+ int callingUid, String callerPackageName) {
+ restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callerPackageName);
+ maybeDisableLocalNetworkMatching(nc, callingUid);
+ }
+
@Override
public @RestrictBackgroundStatus int getRestrictBackgroundStatusByCaller() {
enforceAccessPermission();
@@ -3277,14 +3371,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 {
@@ -3315,44 +3433,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) {
@@ -3369,43 +3535,46 @@
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();
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private void dumpBpfProgramStatus(IndentingPrintWriter pw) {
pw.println("Bpf Program Status:");
pw.increaseIndent();
@@ -3414,12 +3583,37 @@
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_INGRESS));
pw.print("CGROUP_INET_EGRESS: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_EGRESS));
+
pw.print("CGROUP_INET_SOCK_CREATE: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_SOCK_CREATE));
+
pw.print("CGROUP_INET4_BIND: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET4_BIND));
pw.print("CGROUP_INET6_BIND: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET6_BIND));
+
+ pw.print("CGROUP_INET4_CONNECT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET4_CONNECT));
+ pw.print("CGROUP_INET6_CONNECT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET6_CONNECT));
+
+ pw.print("CGROUP_UDP4_SENDMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP4_SENDMSG));
+ pw.print("CGROUP_UDP6_SENDMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP6_SENDMSG));
+
+ pw.print("CGROUP_UDP4_RECVMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP4_RECVMSG));
+ pw.print("CGROUP_UDP6_RECVMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP6_RECVMSG));
+
+ pw.print("CGROUP_GETSOCKOPT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_GETSOCKOPT));
+ pw.print("CGROUP_SETSOCKOPT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_SETSOCKOPT));
+
+ pw.print("CGROUP_INET_SOCK_RELEASE: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_SOCK_RELEASE));
} catch (IOException e) {
pw.println(" IOException");
}
@@ -3428,16 +3622,11 @@
@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 =
"allow_sysui_connectivity_reports";
- public static final String LOG_BPF_RC = "log_bpf_rc_force_disable";
-
public static final String ALLOW_SATALLITE_NETWORK_FALLBACK =
"allow_satallite_network_fallback";
@@ -3811,6 +4000,10 @@
mSatelliteAccessController.start();
}
+ if (mCarrierPrivilegeAuthenticator != null) {
+ mCarrierPrivilegeAuthenticator.start();
+ }
+
// On T+ devices, register callback for statsd to pull NETWORK_BPF_MAP_INFO atom
if (mDeps.isAtLeastT()) {
mBpfNetMaps.setPullAtomCallback(mContext);
@@ -3989,6 +4182,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:");
@@ -4062,10 +4261,21 @@
dumpAvoidBadWifiSettings(pw);
pw.println();
- dumpCloseFrozenAppSockets(pw);
+ dumpDestroySockets(pw);
- pw.println();
- dumpBpfProgramStatus(pw);
+ if (mDeps.isAtLeastT()) {
+ // R: https://android.googlesource.com/platform/system/core/+/refs/heads/android11-release/rootdir/init.rc
+ // shows /dev/cg2_bpf
+ // S: https://android.googlesource.com/platform/system/core/+/refs/heads/android12-release/rootdir/init.rc
+ // does not
+ // Thus cgroups are mounted at /dev/cg2_bpf on R and not on /sys/fs/cgroup
+ // so the following won't work (on R) anyway.
+ // The /sys/fs/cgroup path is only actually enforced/required starting with U,
+ // but it is very likely to already be the case (though not guaranteed) on T.
+ // I'm not at all sure about S - let's just skip it to get rid of lint warnings.
+ pw.println();
+ dumpBpfProgramStatus(pw);
+ }
if (null != mCarrierPrivilegeAuthenticator) {
pw.println();
@@ -4136,6 +4346,9 @@
pw.println();
pw.println("Multicast routing supported: " +
(mMulticastRoutingCoordinatorService != null));
+
+ pw.println();
+ pw.println("Background firewall chain enabled: " + mBackgroundFirewallChainEnabled);
}
private void dumpNetworks(IndentingPrintWriter pw) {
@@ -4245,6 +4458,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
@@ -4622,7 +4844,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();
@@ -5172,7 +5402,7 @@
if (mDefaultRequest == nri) {
mNetworkActivityTracker.updateDefaultNetwork(null /* newNetwork */, nai);
- maybeClosePendingFrozenSockets(null /* newNetwork */, nai);
+ maybeDestroyPendingSockets(null /* newNetwork */, nai);
ensureNetworkTransitionWakelock(nai.toShortString());
}
}
@@ -5332,6 +5562,7 @@
// was is being disconnected the callbacks have already been sent, and if it is being
// destroyed pending replacement they will be sent when it is disconnected.
maybeDisableForwardRulesForDisconnectingNai(nai, false /* sendCallbacks */);
+ updateIngressToVpnAddressFiltering(null, nai.linkProperties, nai);
try {
mNetd.networkDestroy(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
@@ -5448,6 +5679,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()) {
@@ -5647,6 +5884,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)) {
@@ -5681,7 +5923,9 @@
}
}
- nri.mPerUidCounter.decrementCount(nri.mUid);
+ if (untrackUids) {
+ maybeUntrackUidAndClearBlockedReasons(nri);
+ }
mNetworkRequestInfoLogs.log("RELEASE " + nri);
checkNrisConsistency(nri);
@@ -5703,12 +5947,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);
}
}
@@ -6009,7 +6258,15 @@
if (nm == null) return;
if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
- enforceNetworkStackPermission(mContext);
+ // This enforceNetworkStackPermission() should be adopted to check
+ // the required permission but this may be break OEM captive portal
+ // apps. Simply ignore the request if the caller does not have
+ // permission.
+ if (!hasNetworkStackPermission()) {
+ Log.e(TAG, "Calling appRequest() without proper permission. Skip");
+ return;
+ }
+
nm.forceReevaluation(mDeps.getCallingUid());
}
}
@@ -6440,8 +6697,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);
@@ -6490,8 +6747,11 @@
UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj;
handleFrozenUids(args.mUids, args.mFrozenStates);
break;
- case EVENT_UID_CARRIER_PRIVILEGES_LOST:
- handleUidCarrierPrivilegesLost(msg.arg1, msg.arg2);
+ case EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS:
+ handleUpdateFirewallDestroySocketReasons((List) msg.obj);
+ break;
+ case EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS:
+ handleClearFirewallDestroySocketReasons(msg.arg1);
break;
}
}
@@ -7309,9 +7569,16 @@
// 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;
+ final int mDeclaredMethodsFlags;
+
// In order to preserve the mapping of NetworkRequest-to-callback when apps register
// callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
// maintained for keying off of. This is only a concern when the original nri
@@ -7358,7 +7625,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}.
@@ -7366,21 +7632,22 @@
mCallbackFlags = NetworkCallback.FLAG_NONE;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = preferenceOrder;
+ mDeclaredMethodsFlags = DECLARED_METHODS_NONE;
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
this(asUid, Collections.singletonList(r), r, m, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlags);
}
NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
@NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
super();
ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
@@ -7392,10 +7659,10 @@
mAsUid = asUid;
mPendingIntent = null;
mPerUidCounter = getRequestCounter(this);
- mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = callbackFlags;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
+ mDeclaredMethodsFlags = declaredMethodsFlags;
linkDeathRecipient();
}
@@ -7432,10 +7699,11 @@
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;
+ mDeclaredMethodsFlags = nri.mDeclaredMethodsFlags;
linkDeathRecipient();
}
@@ -7522,7 +7790,9 @@
+ " " + mRequests
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
+ " callback flags: " + mCallbackFlags
- + " order: " + mPreferenceOrder;
+ + " order: " + mPreferenceOrder
+ + " isUidTracked: " + mUidTrackedForBlockedStatus
+ + " declaredMethods: 0x" + Integer.toHexString(mDeclaredMethodsFlags);
}
}
@@ -7572,15 +7842,6 @@
"Insufficient permissions to request a specific signal strength");
}
mAppOpsManager.checkPackage(callerUid, callerPackageName);
-
- if (nc.getSubscriptionIds().isEmpty()) {
- return;
- }
- if (mRequestRestrictedWifiEnabled
- && canRequestRestrictedNetworkDueToCarrierPrivileges(nc, callerUid)) {
- return;
- }
- enforceNetworkFactoryPermission();
}
private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
@@ -7669,7 +7930,21 @@
public NetworkRequest requestNetwork(int asUid, NetworkCapabilities networkCapabilities,
int reqTypeInt, Messenger messenger, int timeoutMs, final IBinder binder,
int legacyType, int callbackFlags, @NonNull String callingPackageName,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlag) {
+ if (declaredMethodsFlag == 0) {
+ // This could happen if raw binder calls are used to call the previous overload of
+ // requestNetwork, as missing int arguments in a binder call end up as 0
+ // (Parcel.readInt returns 0 at the end of a parcel). Such raw calls this would be
+ // really unexpected bad behavior from the caller though.
+ // TODO: remove after verifying this does not happen. This could allow enabling the
+ // optimization for callbacks that do not override any method (right now they use
+ // DECLARED_METHODS_ALL), if it is OK to break NetworkCallbacks created using
+ // dexmaker-mockito-inline and either spy() or MockSettings.useConstructor (see
+ // comment in ConnectivityManager which sets the flag to DECLARED_METHODS_ALL).
+ Log.wtf(TAG, "requestNetwork called without declaredMethodsFlag from "
+ + callingPackageName);
+ declaredMethodsFlag = DECLARED_METHODS_ALL;
+ }
if (legacyType != TYPE_NONE && !hasNetworkStackPermission()) {
if (isTargetSdkAtleast(Build.VERSION_CODES.M, mDeps.getCallingUid(),
callingPackageName)) {
@@ -7714,10 +7989,12 @@
// the state of the app when the request is filed, but we never change the
// request if the app changes network state. http://b/29964605
enforceMeteredApnPolicy(networkCapabilities);
+ maybeDisableLocalNetworkMatching(networkCapabilities, callingUid);
break;
case LISTEN_FOR_BEST:
enforceAccessPermission();
networkCapabilities = new NetworkCapabilities(networkCapabilities);
+ maybeDisableLocalNetworkMatching(networkCapabilities, callingUid);
break;
default:
throw new IllegalArgumentException("Unsupported request type " + reqType);
@@ -7744,13 +8021,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
@@ -7762,7 +8032,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, declaredMethodsFlag);
+ 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);
@@ -7786,7 +8062,7 @@
private NetworkRequestInfo getNriToRegister(final int asUid, @NonNull final NetworkRequest nr,
@Nullable final Messenger msgr, @Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
final List<NetworkRequest> requests;
if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
requests = copyDefaultNetworkRequestsForUid(
@@ -7795,7 +8071,8 @@
requests = Collections.singletonList(nr);
}
return new NetworkRequestInfo(
- asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
+ asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag,
+ declaredMethodsFlags);
}
private boolean shouldCheckCapabilitiesDeclaration(
@@ -7804,7 +8081,7 @@
final UserHandle user = UserHandle.getUserHandleForUid(callingUid);
// Only run the check if the change is enabled.
if (!mDeps.isChangeEnabled(
- ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION,
+ ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION,
callingPackageName, user)) {
return false;
}
@@ -7932,6 +8209,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)) {
@@ -7956,16 +8240,15 @@
ensureRequestableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
- restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
- callingUid, callingPackageName);
+ restrictRequestNetworkCapabilitiesForCaller(
+ networkCapabilities, callingUid, callingPackageName);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
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;
}
@@ -8004,11 +8287,88 @@
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,
@NetworkCallback.Flag int callbackFlags,
- @NonNull String callingPackageName, @NonNull String callingAttributionTag) {
+ @NonNull String callingPackageName, @NonNull String callingAttributionTag,
+ int declaredMethodsFlag) {
+ if (declaredMethodsFlag == 0) {
+ Log.wtf(TAG, "listenForNetwork called without declaredMethodsFlag from "
+ + callingPackageName);
+ declaredMethodsFlag = DECLARED_METHODS_ALL;
+ }
final int callingUid = mDeps.getCallingUid();
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
@@ -8017,7 +8377,7 @@
NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
- restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName);
+ restrictRequestNetworkCapabilitiesForCaller(nc, callingUid, callingPackageName);
// Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so
// make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get
// onLost and onAvailable callbacks when networks move in and out of the background.
@@ -8030,10 +8390,10 @@
NetworkRequest.Type.LISTEN);
NetworkRequestInfo nri =
new NetworkRequestInfo(callingUid, networkRequest, messenger, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlag);
if (VDBG) log("listenForNetwork for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_LISTENER, nri);
return networkRequest;
}
@@ -8050,7 +8410,7 @@
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
- restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName);
+ restrictRequestNetworkCapabilitiesForCaller(nc, callingUid, callingPackageName);
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
@@ -8058,8 +8418,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. */
@@ -8681,6 +9040,8 @@
// new interface (the interface name -> index map becomes initialized)
updateVpnFiltering(newLp, oldLp, networkAgent);
+ updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent);
+
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -8972,6 +9333,87 @@
}
}
+ /**
+ * Returns ingress discard rules to drop packets to VPN addresses ingressing via non-VPN
+ * interfaces.
+ * Ingress discard rule is added to the address iff
+ * 1. The address is not a link local address
+ * 2. The address is used by a single VPN interface and not used by any other
+ * interfaces even non-VPN ones
+ * This method can be called during network disconnects, when nai has already been removed from
+ * mNetworkAgentInfos.
+ *
+ * @param nai This method generates rules assuming lp of this nai is the lp at the second
+ * argument.
+ * @param lp This method generates rules assuming lp of nai at the first argument is this lp.
+ * Caller passes old lp to generate old rules and new lp to generate new rules.
+ * @return ingress discard rules. Set of pairs of addresses and interface names
+ */
+ private Set<Pair<InetAddress, String>> generateIngressDiscardRules(
+ @NonNull final NetworkAgentInfo nai, @Nullable final LinkProperties lp) {
+ Set<NetworkAgentInfo> nais = new ArraySet<>(mNetworkAgentInfos);
+ nais.add(nai);
+ // Determine how many networks each IP address is currently configured on.
+ // Ingress rules are added only for IP addresses that are configured on single interface.
+ final Map<InetAddress, Integer> addressOwnerCounts = new ArrayMap<>();
+ for (final NetworkAgentInfo agent : nais) {
+ if (agent.isDestroyed()) {
+ continue;
+ }
+ final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties;
+ if (agentLp == null) {
+ continue;
+ }
+ for (final InetAddress addr: agentLp.getAllAddresses()) {
+ addressOwnerCounts.put(addr, addressOwnerCounts.getOrDefault(addr, 0) + 1);
+ }
+ }
+
+ // Iterates all networks instead of only generating rule for nai that was passed in since
+ // lp of the nai change could cause/resolve address collision and result in affecting rule
+ // for different network.
+ final Set<Pair<InetAddress, String>> ingressDiscardRules = new ArraySet<>();
+ for (final NetworkAgentInfo agent : nais) {
+ if (!agent.isVPN() || agent.isDestroyed()) {
+ continue;
+ }
+ final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties;
+ if (agentLp == null || agentLp.getInterfaceName() == null) {
+ continue;
+ }
+
+ for (final InetAddress addr: agentLp.getAllAddresses()) {
+ if (addressOwnerCounts.get(addr) == 1 && !addr.isLinkLocalAddress()) {
+ ingressDiscardRules.add(new Pair<>(addr, agentLp.getInterfaceName()));
+ }
+ }
+ }
+ return ingressDiscardRules;
+ }
+
+ private void updateIngressToVpnAddressFiltering(@Nullable LinkProperties newLp,
+ @Nullable LinkProperties oldLp, @NonNull NetworkAgentInfo nai) {
+ // Having isAtleastT to avoid NewApi linter error (b/303382209)
+ if (!mIngressToVpnAddressFiltering || !mDeps.isAtLeastT()) {
+ return;
+ }
+ final CompareOrUpdateResult<InetAddress, Pair<InetAddress, String>> ruleDiff =
+ new CompareOrUpdateResult<>(
+ generateIngressDiscardRules(nai, oldLp),
+ generateIngressDiscardRules(nai, newLp),
+ (rule) -> rule.first);
+ for (Pair<InetAddress, String> rule: ruleDiff.removed) {
+ mBpfNetMaps.removeIngressDiscardRule(rule.first);
+ }
+ for (Pair<InetAddress, String> rule: ruleDiff.added) {
+ mBpfNetMaps.setIngressDiscardRule(rule.first, rule.second);
+ }
+ // setIngressDiscardRule overrides the existing rule
+ for (Pair<InetAddress, String> rule: ruleDiff.updated) {
+ mBpfNetMaps.setIngressDiscardRule(rule.first, rule.second);
+ }
+ }
+
private void updateWakeOnLan(@NonNull LinkProperties lp) {
if (mWolSupportedInterfaces == null) {
mWolSupportedInterfaces = new ArraySet<>(mResources.get().getStringArray(
@@ -9155,6 +9597,9 @@
}
private void handleUidCarrierPrivilegesLost(int uid, int subId) {
+ if (!mRequestRestrictedWifiEnabled) {
+ return;
+ }
ensureRunningOnConnectivityServiceThread();
// A NetworkRequest needs to be revoked when all the conditions are met
// 1. It requests restricted network
@@ -9162,7 +9607,7 @@
// 3. The app doesn't have Carrier Privileges
// 4. The app doesn't have permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
for (final NetworkRequest nr : mNetworkRequests.keySet()) {
- if ((nr.isRequest() || nr.isListen())
+ if (nr.isRequest()
&& !nr.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
&& nr.getRequestorUid() == uid
&& getSubscriptionIdFromNetworkCaps(nr.networkCapabilities) == subId
@@ -9387,7 +9832,8 @@
configBuilder.setUpstreamSelector(nr);
final NetworkRequestInfo nri = new NetworkRequestInfo(
nai.creatorUid, nr, null /* messenger */, null /* binder */,
- 0 /* callbackFlags */, null /* attributionTag */);
+ 0 /* callbackFlags */, null /* attributionTag */,
+ DECLARED_METHODS_NONE);
if (null != oldSatisfier) {
// Set the old satisfier in the new NRI so that the rematch will see any changes
nri.setSatisfier(oldSatisfier, nr);
@@ -9758,6 +10204,11 @@
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
+ if (mUseDeclaredMethodsForCallbacksEnabled
+ && (nri.mDeclaredMethodsFlags & (1 << notificationType)) == 0) {
+ // No need to send the notification as the recipient method is not overridden
+ return;
+ }
final Bundle bundle = new Bundle();
// TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
// TODO: check if defensive copies of data is needed.
@@ -9956,7 +10407,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);
@@ -10928,7 +11379,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.
@@ -10937,7 +11388,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
@@ -10978,8 +11443,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);
@@ -10998,8 +11465,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;
}
@@ -11365,6 +11833,10 @@
return 0;
}
case "get-package-networking-enabled": {
+ if (!mDeps.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ "This command is not supported on T-");
+ }
final String packageName = getNextArg();
final int rule = getPackageFirewallRule(
ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3, packageName);
@@ -11394,6 +11866,10 @@
return 0;
}
case "get-background-networking-enabled-for-uid": {
+ if (!mDeps.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ "This command is not supported on T-");
+ }
final Integer uid = parseIntegerArgument(getNextArg());
if (null == uid) {
onHelp();
@@ -12023,7 +12499,7 @@
// This NetworkCapabilities is only used for matching to Networks. Clear out its owner uid
// and administrator uids to be safe.
final NetworkCapabilities nc = new NetworkCapabilities(request.networkCapabilities);
- restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName);
+ restrictRequestNetworkCapabilitiesForCaller(nc, callingUid, callingPackageName);
final NetworkRequest requestWithId =
new NetworkRequest(
@@ -12035,6 +12511,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);
@@ -12630,8 +13107,8 @@
if (um.isManagedProfile(profile.getIdentifier())) {
return true;
}
- if (mDeps.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
- return false;
+
+ return mDeps.isAtLeastT() && dpm.getDeviceOwnerComponentOnAnyUser() != null;
}
/**
@@ -12896,11 +13373,12 @@
requests.add(createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
- // request: restricted Satellite internet
+ // request: Satellite internet, satellite network could be restricted or constrained
final NetworkCapabilities cap = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
.build();
requests.add(createNetworkRequest(NetworkRequest.Type.REQUEST, cap));
@@ -13129,7 +13607,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);
@@ -13364,40 +13855,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);
}
}
@@ -13420,6 +13888,12 @@
public void setUidFirewallRule(final int chain, final int uid, final int rule) {
enforceNetworkStackOrSettingsPermission();
+ if (chain == FIREWALL_CHAIN_BACKGROUND && !mBackgroundFirewallChainEnabled) {
+ Log.i(TAG, "Ignoring operation setUidFirewallRule on the background chain because the"
+ + " feature is disabled.");
+ return;
+ }
+
// There are only two type of firewall rule: FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
int firewallRule = getFirewallRuleType(chain, rule);
@@ -13427,13 +13901,24 @@
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));
+ }
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private int getPackageFirewallRule(final int chain, final String packageName)
throws PackageManager.NameNotFoundException {
final PackageManager pm = mContext.getPackageManager();
@@ -13441,6 +13926,7 @@
return getUidFirewallRule(chain, appId);
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Override
public int getUidFirewallRule(final int chain, final int uid) {
enforceNetworkStackOrSettingsPermission();
@@ -13454,6 +13940,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:
@@ -13461,6 +13949,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:
@@ -13471,31 +13960,71 @@
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();
- try {
- mBpfNetMaps.setChildChain(chain, enable);
- } catch (ServiceSpecificException e) {
- throw new IllegalStateException(e);
+ if (chain == FIREWALL_CHAIN_BACKGROUND && !mBackgroundFirewallChainEnabled) {
+ Log.i(TAG, "Ignoring operation setFirewallChainEnabled on the background chain because"
+ + " 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");
+ }
+
+ 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) {
@@ -13507,10 +14036,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);
}
@@ -13518,7 +14095,37 @@
public void replaceFirewallChain(final int chain, final int[] uids) {
enforceNetworkStackOrSettingsPermission();
- mBpfNetMaps.replaceUidChain(chain, uids);
+ if (chain == FIREWALL_CHAIN_BACKGROUND && !mBackgroundFirewallChainEnabled) {
+ Log.i(TAG, "Ignoring operation replaceFirewallChain on the background chain because"
+ + " the feature is disabled.");
+ return;
+ }
+
+ 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
@@ -13532,4 +14139,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/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 843b7b3..4d39d7d 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -267,6 +267,7 @@
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
nc.setNetworkSpecifier(new TestNetworkSpecifier(iface));
nc.setAdministratorUids(administratorUids);
if (!isMetered) {
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index 04d0fc1..f5fa4fb 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -91,42 +91,62 @@
@NonNull final TelephonyManager t,
@NonNull final TelephonyManagerShim telephonyManagerShim,
final boolean requestRestrictedWifiEnabled,
- @NonNull BiConsumer<Integer, Integer> listener) {
+ @NonNull BiConsumer<Integer, Integer> listener,
+ @NonNull final Handler connectivityServiceHandler) {
mContext = c;
mTelephonyManager = t;
mTelephonyManagerShim = telephonyManagerShim;
- final HandlerThread thread = deps.makeHandlerThread();
- thread.start();
- mHandler = new Handler(thread.getLooper());
mUseCallbacksForServiceChanged = deps.isFeatureEnabled(
c, CARRIER_SERVICE_CHANGED_USE_CALLBACK);
mRequestRestrictedWifiEnabled = requestRestrictedWifiEnabled;
mListener = listener;
+ if (mRequestRestrictedWifiEnabled) {
+ mHandler = connectivityServiceHandler;
+ } else {
+ final HandlerThread thread = deps.makeHandlerThread();
+ thread.start();
+ mHandler = new Handler(thread.getLooper());
+ synchronized (mLock) {
+ registerSimConfigChangedReceiver();
+ simConfigChanged();
+ }
+ }
+ }
+
+ private void registerSimConfigChangedReceiver() {
final IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
- synchronized (mLock) {
- // Never unregistered because the system server never stops
- c.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- switch (intent.getAction()) {
- case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
- simConfigChanged();
- break;
- default:
- Log.d(TAG, "Unknown intent received, action: " + intent.getAction());
- }
+ // Never unregistered because the system server never stops
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ switch (intent.getAction()) {
+ case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
+ simConfigChanged();
+ break;
+ default:
+ Log.d(TAG, "Unknown intent received, action: " + intent.getAction());
}
- }, filter, null, mHandler);
- simConfigChanged();
+ }
+ }, filter, null, mHandler);
+ }
+
+ /**
+ * Start CarrierPrivilegeAuthenticator
+ */
+ public void start() {
+ if (mRequestRestrictedWifiEnabled) {
+ registerSimConfigChangedReceiver();
+ mHandler.post(this::simConfigChanged);
}
}
public CarrierPrivilegeAuthenticator(@NonNull final Context c,
@NonNull final TelephonyManager t, final boolean requestRestrictedWifiEnabled,
- @NonNull BiConsumer<Integer, Integer> listener) {
+ @NonNull BiConsumer<Integer, Integer> listener,
+ @NonNull final Handler connectivityServiceHandler) {
this(c, new Dependencies(), t, TelephonyManagerShimImpl.newInstance(t),
- requestRestrictedWifiEnabled, listener);
+ requestRestrictedWifiEnabled, listener, connectivityServiceHandler);
}
public static class Dependencies {
@@ -146,6 +166,10 @@
}
private void simConfigChanged() {
+ // If mRequestRestrictedWifiEnabled is false, constructor calls simConfigChanged
+ if (mRequestRestrictedWifiEnabled) {
+ ensureRunningOnHandlerThread();
+ }
synchronized (mLock) {
unregisterCarrierPrivilegesListeners();
mModemCount = mTelephonyManager.getActiveModemCount();
@@ -188,6 +212,7 @@
public void onCarrierPrivilegesChanged(
@NonNull List<String> privilegedPackageNames,
@NonNull int[] privilegedUids) {
+ ensureRunningOnHandlerThread();
if (mUseCallbacksForServiceChanged) return;
// Re-trigger the synchronous check (which is also very cheap due
// to caching in CarrierPrivilegesTracker). This allows consistency
@@ -198,6 +223,7 @@
@Override
public void onCarrierServiceChanged(@Nullable final String carrierServicePackageName,
final int carrierServiceUid) {
+ ensureRunningOnHandlerThread();
if (!mUseCallbacksForServiceChanged) {
// Re-trigger the synchronous check (which is also very cheap due
// to caching in CarrierPrivilegesTracker). This allows consistency
@@ -439,6 +465,13 @@
}
}
+ private void ensureRunningOnHandlerThread() {
+ if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Not running on handler thread: " + Thread.currentThread().getName());
+ }
+ }
+
public void dump(IndentingPrintWriter pw) {
pw.println("CarrierPrivilegeAuthenticator:");
pw.println("mRequestRestrictedWifiEnabled = " + mRequestRestrictedWifiEnabled);
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index daaf91d..b1c770b 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -41,9 +41,11 @@
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;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.TcUtils;
import com.android.net.module.util.bpf.ClatEgress4Key;
import com.android.net.module.util.bpf.ClatEgress4Value;
@@ -255,7 +257,7 @@
@Nullable
public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
try {
- return new BpfMap<>(CLAT_INGRESS6_MAP_PATH,
+ return SingleWriterBpfMap.getSingleton(CLAT_INGRESS6_MAP_PATH,
ClatIngress6Key.class, ClatIngress6Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create ingress6 map: " + e);
@@ -267,7 +269,7 @@
@Nullable
public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
try {
- return new BpfMap<>(CLAT_EGRESS4_MAP_PATH,
+ return SingleWriterBpfMap.getSingleton(CLAT_EGRESS4_MAP_PATH,
ClatEgress4Key.class, ClatEgress4Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create egress4 map: " + e);
@@ -279,6 +281,7 @@
@Nullable
public IBpfMap<CookieTagMapKey, CookieTagMapValue> getBpfCookieTagMap() {
try {
+ // also read and written from other locations
return new BpfMap<>(COOKIE_TAG_MAP_PATH,
CookieTagMapKey.class, CookieTagMapValue.class);
} catch (ErrnoException e) {
@@ -399,6 +402,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 +527,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 +582,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 +589,123 @@
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) {
+ 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);
+ }
+ }
}
-
- // [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) {
@@ -847,12 +771,12 @@
if (mIngressMap.isEmpty()) {
pw.println("<empty>");
}
- pw.println("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif");
+ pw.println("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif (packets bytes)");
pw.increaseIndent();
mIngressMap.forEach((k, v) -> {
// TODO: print interface name
- pw.println(String.format("%d %s/96 %s -> %s %d", k.iif, k.pfx96, k.local6,
- v.local4, v.oif));
+ pw.println(String.format("%d %s/96 %s -> %s %d (%d %d)", k.iif, k.pfx96, k.local6,
+ v.local4, v.oif, v.packets, v.bytes));
});
pw.decreaseIndent();
} catch (ErrnoException e) {
@@ -870,12 +794,13 @@
if (mEgressMap.isEmpty()) {
pw.println("<empty>");
}
- pw.println("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif");
+ pw.println("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif (packets bytes)");
pw.increaseIndent();
mEgressMap.forEach((k, v) -> {
// TODO: print interface name
- pw.println(String.format("%d %s -> %s %s/96 %d %s", k.iif, k.local4, v.local6,
- v.pfx96, v.oif, v.oifIsEthernet != 0 ? "ether" : "rawip"));
+ pw.println(String.format("%d %s -> %s %s/96 %d %s (%d %d)", k.iif, k.local4,
+ v.local6, v.pfx96, v.oif, v.oifIsEthernet != 0 ? "ether" : "rawip",
+ v.packets, v.bytes));
});
pw.decreaseIndent();
} catch (ErrnoException e) {
@@ -884,6 +809,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 bf09160..1ee1ed7 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -38,6 +38,17 @@
public static final String REQUEST_RESTRICTED_WIFI =
"request_restricted_wifi";
+
+ public static final String INGRESS_TO_VPN_ADDRESS_FILTERING =
+ "ingress_to_vpn_address_filtering";
+
+ 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/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
index 48af9fa..21dbb45 100644
--- a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
@@ -29,6 +29,7 @@
import android.net.TelephonyNetworkSpecifier;
import android.net.TransportInfo;
import android.net.wifi.WifiInfo;
+import android.os.Build;
import android.os.Handler;
import android.os.SystemClock;
import android.telephony.SubscriptionInfo;
@@ -39,6 +40,8 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.metrics.DailykeepaliveInfoReported;
import com.android.metrics.DurationForNumOfKeepalive;
@@ -279,6 +282,7 @@
*
* @param dailyKeepaliveInfoReported the proto to write to statsD.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void writeStats(DailykeepaliveInfoReported dailyKeepaliveInfoReported) {
ConnectivityStatsLog.write(
ConnectivityStatsLog.DAILY_KEEPALIVE_INFO_REPORTED,
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/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 7707122..fd41ee6 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -170,9 +170,11 @@
&& !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData()
.getVenueFriendlyName())) {
name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName();
+ } else if (!TextUtils.isEmpty(extraInfo)) {
+ name = extraInfo;
} else {
- name = TextUtils.isEmpty(extraInfo)
- ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo;
+ final String ssid = WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid());
+ name = ssid == null ? "" : ssid;
}
// Only notify for Internet-capable networks.
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
diff --git a/service/src/com/android/server/connectivity/SatelliteAccessController.java b/service/src/com/android/server/connectivity/SatelliteAccessController.java
index b53abce..2cdc932 100644
--- a/service/src/com/android/server/connectivity/SatelliteAccessController.java
+++ b/service/src/com/android/server/connectivity/SatelliteAccessController.java
@@ -20,7 +20,10 @@
import android.annotation.NonNull;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
@@ -49,7 +52,6 @@
private final Context mContext;
private final Dependencies mDeps;
private final DefaultMessageRoleListener mDefaultMessageRoleListener;
- private final UserManager mUserManager;
private final Consumer<Set<Integer>> mCallback;
private final Handler mConnectivityServiceHandler;
@@ -114,7 +116,6 @@
@NonNull final Handler connectivityServiceInternalHandler) {
mContext = c;
mDeps = deps;
- mUserManager = mContext.getSystemService(UserManager.class);
mDefaultMessageRoleListener = new DefaultMessageRoleListener();
mCallback = callback;
mConnectivityServiceHandler = connectivityServiceInternalHandler;
@@ -165,9 +166,6 @@
}
// on Role sms change triggered by OnRoleHoldersChangedListener()
- // TODO(b/326373613): using UserLifecycleListener, callback to be received when user removed for
- // user delete scenario. This to be used to update uid list and ML Layer request can also be
- // updated.
private void onRoleSmsChanged(@NonNull UserHandle userHandle) {
int userId = userHandle.getIdentifier();
if (userId == Process.INVALID_UID) {
@@ -184,9 +182,8 @@
mAllUsersSatelliteNetworkFallbackUidCache.get(userId, new ArraySet<>());
Log.i(TAG, "currentUser : role_sms_packages: " + userId + " : " + packageNames);
- final Set<Integer> newUidsForUser = !packageNames.isEmpty()
- ? updateSatelliteNetworkFallbackUidListCache(packageNames, userHandle)
- : new ArraySet<>();
+ final Set<Integer> newUidsForUser =
+ updateSatelliteNetworkFallbackUidListCache(packageNames, userHandle);
Log.i(TAG, "satellite_fallback_uid: " + newUidsForUser);
// on Role change, update the multilayer request at ConnectivityService with updated
@@ -197,6 +194,11 @@
mAllUsersSatelliteNetworkFallbackUidCache.put(userId, newUidsForUser);
+ // Update all users fallback cache for user, send cs fallback to update ML request
+ reportSatelliteNetworkFallbackUids();
+ }
+
+ private void reportSatelliteNetworkFallbackUids() {
// Merge all uids of multiple users available
Set<Integer> mergedSatelliteNetworkFallbackUidCache = new ArraySet<>();
for (int i = 0; i < mAllUsersSatelliteNetworkFallbackUidCache.size(); i++) {
@@ -210,27 +212,48 @@
mCallback.accept(mergedSatelliteNetworkFallbackUidCache);
}
- private List<String> getRoleSmsChangedPackageName(UserHandle userHandle) {
- try {
- return mDeps.getRoleHoldersAsUser(RoleManager.ROLE_SMS, userHandle);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Could not get package name at role sms change update due to: " + e);
- return null;
- }
- }
-
- /** Register OnRoleHoldersChangedListener */
public void start() {
mConnectivityServiceHandler.post(this::updateAllUserRoleSmsUids);
+
+ // register sms OnRoleHoldersChangedListener
mDefaultMessageRoleListener.register();
+
+ // Monitor for User removal intent, to update satellite fallback uids.
+ IntentFilter userRemovedFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ final UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (userHandle == null) return;
+ updateSatelliteFallbackUidListOnUserRemoval(userHandle.getIdentifier());
+ } else {
+ Log.wtf(TAG, "received unexpected intent: " + action);
+ }
+ }
+ }, userRemovedFilter, null, mConnectivityServiceHandler);
+
}
private void updateAllUserRoleSmsUids() {
- List<UserHandle> existingUsers = mUserManager.getUserHandles(true /* excludeDying */);
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ // get existing user handles of available users
+ List<UserHandle> existingUsers = userManager.getUserHandles(true /*excludeDying*/);
+
// Iterate through the user handles and obtain their uids with role sms and satellite
// communication permission
+ Log.i(TAG, "existing users: " + existingUsers);
for (UserHandle userHandle : existingUsers) {
onRoleSmsChanged(userHandle);
}
}
+
+ private void updateSatelliteFallbackUidListOnUserRemoval(int userIdRemoved) {
+ Log.i(TAG, "user id removed:" + userIdRemoved);
+ if (mAllUsersSatelliteNetworkFallbackUidCache.contains(userIdRemoved)) {
+ mAllUsersSatelliteNetworkFallbackUidCache.remove(userIdRemoved);
+ reportSatelliteNetworkFallbackUids();
+ }
+ }
}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index f7b42a6..34ea9ab 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -39,12 +39,13 @@
"device/com/android/net/module/util/DeviceConfigUtils.java",
"device/com/android/net/module/util/DomainUtils.java",
"device/com/android/net/module/util/FdEventsReader.java",
+ "device/com/android/net/module/util/FeatureVersions.java",
+ "device/com/android/net/module/util/HandlerUtils.java",
"device/com/android/net/module/util/NetworkMonitorUtils.java",
"device/com/android/net/module/util/PacketReader.java",
"device/com/android/net/module/util/SharedLog.java",
"device/com/android/net/module/util/SocketUtils.java",
- "device/com/android/net/module/util/FeatureVersions.java",
- "device/com/android/net/module/util/HandlerUtils.java",
+ "device/com/android/net/module/util/SyncStateMachine.java",
// This library is used by system modules, for which the system health impact of Kotlin
// has not yet been evaluated. Annotations may need jarjar'ing.
// "src_devicecommon/**/*.kt",
@@ -68,6 +69,7 @@
"//packages/modules/CaptivePortalLogin",
],
static_libs: [
+ "modules-utils-statemachine",
"net-utils-framework-common",
],
libs: [
@@ -124,6 +126,8 @@
],
}
+// The net-utils-device-common-bpf library requires the callers to contain
+// net-utils-device-common-struct-base.
java_library {
name: "net-utils-device-common-bpf",
srcs: [
@@ -133,9 +137,8 @@
"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/Struct.java",
+ "device/com/android/net/module/util/SingleWriterBpfMap.java",
"device/com/android/net/module/util/TcUtils.java",
- "framework/com/android/net/module/util/HexDump.java",
],
sdk_version: "module_current",
min_sdk_version: "30",
@@ -146,6 +149,7 @@
libs: [
"androidx.annotation_annotation",
"framework-connectivity.stubs.module_lib",
+ "net-utils-device-common-struct-base",
],
apex_available: [
"com.android.tethering",
@@ -158,12 +162,9 @@
}
java_library {
- name: "net-utils-device-common-struct",
+ name: "net-utils-device-common-struct-base",
srcs: [
- "device/com/android/net/module/util/Ipv6Utils.java",
- "device/com/android/net/module/util/PacketBuilder.java",
"device/com/android/net/module/util/Struct.java",
- "device/com/android/net/module/util/structs/*.java",
],
sdk_version: "module_current",
min_sdk_version: "30",
@@ -176,6 +177,7 @@
],
libs: [
"androidx.annotation_annotation",
+ "framework-annotations-lib", // Required by InetAddressUtils.java
"framework-connectivity.stubs.module_lib",
],
apex_available: [
@@ -188,26 +190,30 @@
},
}
-// The net-utils-multicast-forwarding-structs library requires the callers to
-// contain net-utils-device-common-bpf.
+// The net-utils-device-common-struct library requires the callers to contain
+// net-utils-device-common-struct-base.
java_library {
- name: "net-utils-multicast-forwarding-structs",
+ name: "net-utils-device-common-struct",
srcs: [
- "device/com/android/net/module/util/structs/StructMf6cctl.java",
- "device/com/android/net/module/util/structs/StructMif6ctl.java",
- "device/com/android/net/module/util/structs/StructMrt6Msg.java",
+ "device/com/android/net/module/util/Ipv6Utils.java",
+ "device/com/android/net/module/util/PacketBuilder.java",
+ "device/com/android/net/module/util/structs/*.java",
],
sdk_version: "module_current",
min_sdk_version: "30",
visibility: [
"//packages/modules/Connectivity:__subpackages__",
+ "//packages/modules/NetworkStack:__subpackages__",
],
libs: [
- // Only Struct.java is needed from "net-utils-device-common-bpf"
- "net-utils-device-common-bpf",
+ "androidx.annotation_annotation",
+ "framework-annotations-lib", // Required by IpUtils.java
+ "framework-connectivity.stubs.module_lib",
+ "net-utils-device-common-struct-base",
],
apex_available: [
"com.android.tethering",
+ "//apex_available:platform",
],
lint: {
strict_updatability_linting: true,
@@ -216,7 +222,7 @@
}
// The net-utils-device-common-netlink library requires the callers to contain
-// net-utils-device-common-struct.
+// net-utils-device-common-struct and net-utils-device-common-struct-base.
java_library {
name: "net-utils-device-common-netlink",
srcs: [
@@ -235,6 +241,7 @@
// statically link here because callers of this library might already have a static
// version linked.
"net-utils-device-common-struct",
+ "net-utils-device-common-struct-base",
],
apex_available: [
"com.android.tethering",
@@ -247,7 +254,7 @@
}
// The net-utils-device-common-ip library requires the callers to contain
-// net-utils-device-common-struct.
+// net-utils-device-common-struct and net-utils-device-common-struct-base.
java_library {
// TODO : this target should probably be folded into net-utils-device-common
name: "net-utils-device-common-ip",
@@ -275,7 +282,7 @@
"//apex_available:platform",
],
lint: {
- baseline_filename: "lint-baseline.xml",
+ strict_updatability_linting: true,
error_checks: ["NewApi"],
},
}
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/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
index da77ae8..0fbc25d 100644
--- a/staticlibs/device/com/android/net/module/util/BpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -15,6 +15,7 @@
*/
package com.android.net.module.util;
+import static android.system.OsConstants.EBUSY;
import static android.system.OsConstants.EEXIST;
import static android.system.OsConstants.ENOENT;
@@ -52,6 +53,9 @@
public static final int BPF_F_RDONLY = 1 << 3;
public static final int BPF_F_WRONLY = 1 << 4;
+ // magic value for jni consumption, invalid from kernel point of view
+ public static final int BPF_F_RDWR_EXCLUSIVE = BPF_F_RDONLY | BPF_F_WRONLY;
+
public static final int BPF_MAP_TYPE_HASH = 1;
private static final int BPF_F_NO_PREALLOC = 1;
@@ -69,6 +73,12 @@
private static ConcurrentHashMap<Pair<String, Integer>, ParcelFileDescriptor> sFdCache =
new ConcurrentHashMap<>();
+ private static ParcelFileDescriptor checkModeExclusivity(ParcelFileDescriptor fd, int mode)
+ throws ErrnoException {
+ if (mode == BPF_F_RDWR_EXCLUSIVE) throw new ErrnoException("cachedBpfFdGet", EBUSY);
+ return fd;
+ }
+
private static ParcelFileDescriptor cachedBpfFdGet(String path, int mode,
int keySize, int valueSize)
throws ErrnoException, NullPointerException {
@@ -79,12 +89,12 @@
var key = Pair.create(path, (mode << 26) ^ (keySize << 16) ^ valueSize);
// unlocked fetch is safe: map is concurrent read capable, and only inserted into
ParcelFileDescriptor fd = sFdCache.get(key);
- if (fd != null) return fd;
+ if (fd != null) return checkModeExclusivity(fd, mode);
// ok, no cached fd present, need to grab a lock
synchronized (BpfMap.class) {
// need to redo the check
fd = sFdCache.get(key);
- if (fd != null) return fd;
+ if (fd != null) return checkModeExclusivity(fd, mode);
// okay, we really haven't opened this before...
fd = ParcelFileDescriptor.adoptFd(nativeBpfFdGet(path, mode, keySize, valueSize));
sFdCache.put(key, fd);
diff --git a/staticlibs/device/com/android/net/module/util/BpfUtils.java b/staticlibs/device/com/android/net/module/util/BpfUtils.java
index cdd6fd7..a41eeba 100644
--- a/staticlibs/device/com/android/net/module/util/BpfUtils.java
+++ b/staticlibs/device/com/android/net/module/util/BpfUtils.java
@@ -39,6 +39,15 @@
public static final int BPF_CGROUP_INET_SOCK_CREATE = 2;
public static final int BPF_CGROUP_INET4_BIND = 8;
public static final int BPF_CGROUP_INET6_BIND = 9;
+ public static final int BPF_CGROUP_INET4_CONNECT = 10;
+ public static final int BPF_CGROUP_INET6_CONNECT = 11;
+ public static final int BPF_CGROUP_UDP4_SENDMSG = 14;
+ public static final int BPF_CGROUP_UDP6_SENDMSG = 15;
+ public static final int BPF_CGROUP_UDP4_RECVMSG = 19;
+ public static final int BPF_CGROUP_UDP6_RECVMSG = 20;
+ public static final int BPF_CGROUP_GETSOCKOPT = 21;
+ public static final int BPF_CGROUP_SETSOCKOPT = 22;
+ public static final int BPF_CGROUP_INET_SOCK_RELEASE = 34;
// Note: This is only guaranteed to be accurate on U+ devices. It is likely to be accurate
// on T+ devices as well, but this is not guaranteed.
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index 5b7cbb8..0426ace 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -17,6 +17,7 @@
package com.android.net.module.util;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_CAPTIVEPORTALLOGIN;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
@@ -203,6 +204,29 @@
() -> getTetheringModuleVersion(context));
}
+ /**
+ * Check whether or not one specific experimental feature for a particular namespace from
+ * {@link DeviceConfig} is enabled by comparing module package version
+ * with current version of property. If this property version is valid, the corresponding
+ * experimental feature would be enabled, otherwise disabled.
+ *
+ * This is useful to ensure that if a module install is rolled back, flags are not left fully
+ * rolled out on a version where they have not been well tested.
+ *
+ * If the feature is disabled by default and enabled by flag push, this method should be used.
+ * If the feature is enabled by default and disabled by flag push (kill switch),
+ * {@link #isCaptivePortalLoginFeatureNotChickenedOut(Context, String)} should be used.
+ *
+ * @param context The global context information about an app environment.
+ * @param name The name of the property to look up.
+ * @return true if this feature is enabled, or false if disabled.
+ */
+ public static boolean isCaptivePortalLoginFeatureEnabled(@NonNull Context context,
+ @NonNull String name) {
+ return isFeatureEnabled(NAMESPACE_CAPTIVEPORTALLOGIN, name, false /* defaultEnabled */,
+ () -> getPackageVersion(context));
+ }
+
private static boolean isFeatureEnabled(@NonNull String namespace,
String name, boolean defaultEnabled, Supplier<Long> packageVersionSupplier) {
final int flagValue = getDeviceConfigPropertyInt(namespace, name, 0 /* default value */);
diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
index 33e5bfa..a2dbd81 100644
--- a/staticlibs/device/com/android/net/module/util/PacketBuilder.java
+++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
@@ -24,10 +24,13 @@
import static com.android.net.module.util.IpUtils.ipChecksum;
import static com.android.net.module.util.IpUtils.tcpChecksum;
import static com.android.net.module.util.IpUtils.udpChecksum;
+import static com.android.net.module.util.NetworkStackConstants.IPPROTO_FRAGMENT;
import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET;
@@ -37,6 +40,7 @@
import androidx.annotation.NonNull;
import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.FragmentHeader;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.TcpHeader;
@@ -47,6 +51,10 @@
import java.net.Inet6Address;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
/**
* The class is used to build a packet.
@@ -210,6 +218,20 @@
}
}
+ private void writeFragmentHeader(ByteBuffer buffer, short nextHeader, int offset,
+ boolean mFlag, int id) throws IOException {
+ if ((offset & 7) != 0) {
+ throw new IOException("Invalid offset value, must be multiple of 8");
+ }
+ final FragmentHeader fragmentHeader = new FragmentHeader(nextHeader,
+ offset | (mFlag ? 1 : 0), id);
+ try {
+ fragmentHeader.writeToByteBuffer(buffer);
+ } catch (IllegalArgumentException | BufferOverflowException e) {
+ throw new IOException("Error writing to buffer: ", e);
+ }
+ }
+
/**
* Finalize the packet.
*
@@ -219,9 +241,31 @@
*/
@NonNull
public ByteBuffer finalizePacket() throws IOException {
+ // If the packet is finalized with L2 mtu greater than or equal to its current size, it will
+ // either return a List of size 1 or throw an IOException if something goes wrong.
+ return finalizePacket(mBuffer.position()).get(0);
+ }
+
+ /**
+ * Finalizes the packet with specified link MTU.
+ *
+ * Call after writing L4 header (no payload) or L4 payload to the buffer used by the builder.
+ * L3 header length, L3 header checksum and L4 header checksum are calculated and written back
+ * after finalization.
+ *
+ * @param l2mtu the maximum size, in bytes, of each individual packet. If the packet size
+ * exceeds the l2mtu, it will be fragmented into smaller packets.
+ * @return a list of packet(s), each containing a portion of the original L3 payload.
+ */
+ @NonNull
+ public List<ByteBuffer> finalizePacket(int l2mtu) throws IOException {
// [1] Finalize IPv4 or IPv6 header.
int ipHeaderOffset = INVALID_OFFSET;
if (mIpv4HeaderOffset != INVALID_OFFSET) {
+ if (mBuffer.position() > l2mtu) {
+ throw new IOException("IPv4 fragmentation is not supported");
+ }
+
ipHeaderOffset = mIpv4HeaderOffset;
// Populate the IPv4 totalLength field.
@@ -243,12 +287,15 @@
}
// [2] Finalize TCP or UDP header.
+ final int ipPayloadOffset;
if (mTcpHeaderOffset != INVALID_OFFSET) {
+ ipPayloadOffset = mTcpHeaderOffset;
// Populate the TCP header checksum field.
mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
ipHeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
mBuffer.position() - mTcpHeaderOffset /* transportLen */));
} else if (mUdpHeaderOffset != INVALID_OFFSET) {
+ ipPayloadOffset = mUdpHeaderOffset;
// Populate the UDP header length field.
mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET,
(short) (mBuffer.position() - mUdpHeaderOffset));
@@ -257,11 +304,81 @@
mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer,
ipHeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
} else {
- throw new IOException("Packet is missing neither TCP nor UDP header");
+ throw new IOException("Packet has neither TCP nor UDP header");
}
- mBuffer.flip();
- return mBuffer;
+ if (mBuffer.position() <= l2mtu) {
+ mBuffer.flip();
+ return Arrays.asList(mBuffer);
+ }
+
+ // IPv6 Packet is fragmented into multiple smaller packets that would fit within the link
+ // MTU.
+ // Refer to https://tools.ietf.org/html/rfc2460
+ //
+ // original packet:
+ // +------------------+--------------+--------------+--//--+----------+
+ // | Unfragmentable | first | second | | last |
+ // | Part | fragment | fragment | .... | fragment |
+ // +------------------+--------------+--------------+--//--+----------+
+ //
+ // fragment packets:
+ // +------------------+--------+--------------+
+ // | Unfragmentable |Fragment| first |
+ // | Part | Header | fragment |
+ // +------------------+--------+--------------+
+ //
+ // +------------------+--------+--------------+
+ // | Unfragmentable |Fragment| second |
+ // | Part | Header | fragment |
+ // +------------------+--------+--------------+
+ // o
+ // o
+ // o
+ // +------------------+--------+----------+
+ // | Unfragmentable |Fragment| last |
+ // | Part | Header | fragment |
+ // +------------------+--------+----------+
+ final List<ByteBuffer> fragments = new ArrayList<>();
+ final int totalPayloadLen = mBuffer.position() - ipPayloadOffset;
+ final int perPacketPayloadLen = l2mtu - ipPayloadOffset - IPV6_FRAGMENT_HEADER_LEN;
+ final short protocol = (short) Byte.toUnsignedInt(
+ mBuffer.get(mIpv6HeaderOffset + IPV6_PROTOCOL_OFFSET));
+ Random random = new Random();
+ final int id = random.nextInt(Integer.MAX_VALUE);
+ int startOffset = 0;
+ // Copy the packet content to a byte array.
+ byte[] packet = new byte[mBuffer.position()];
+ // The ByteBuffer#get(int index, byte[] dst) method is only available in API level 35 and
+ // above. Here, we use a more primitive approach: reposition the ByteBuffer to the beginning
+ // before copying, then return its position to the end afterward.
+ mBuffer.position(0);
+ mBuffer.get(packet);
+ mBuffer.position(packet.length);
+ while (startOffset < totalPayloadLen) {
+ int copyPayloadLen = Math.min(perPacketPayloadLen, totalPayloadLen - startOffset);
+ // The data portion must be broken into segments aligned with 8-octet boundaries.
+ // Therefore, the payload length should be a multiple of 8 bytes for all fragments
+ // except the last one.
+ // See https://datatracker.ietf.org/doc/html/rfc791 section 3.2
+ if (copyPayloadLen != totalPayloadLen - startOffset) {
+ copyPayloadLen &= ~7;
+ }
+ ByteBuffer fragment = ByteBuffer.allocate(ipPayloadOffset + IPV6_FRAGMENT_HEADER_LEN
+ + copyPayloadLen);
+ fragment.put(packet, 0, ipPayloadOffset);
+ writeFragmentHeader(fragment, protocol, startOffset,
+ startOffset + copyPayloadLen < totalPayloadLen, id);
+ fragment.put(packet, ipPayloadOffset + startOffset, copyPayloadLen);
+ fragment.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
+ (short) (IPV6_FRAGMENT_HEADER_LEN + copyPayloadLen));
+ fragment.put(mIpv6HeaderOffset + IPV6_PROTOCOL_OFFSET, (byte) IPPROTO_FRAGMENT);
+ fragment.flip();
+ fragments.add(fragment);
+ startOffset += copyPayloadLen;
+ }
+
+ return fragments;
}
/**
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..cd6bfec
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
@@ -0,0 +1,143 @@
+/*
+ * 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 android.util.Pair;
+
+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<>();
+
+ // This should only ever be called (hence private) once for a given 'path'.
+ // Java-wise what matters is the entire {path, key, value} triplet,
+ // but of course the kernel exclusive lock is just on the path (fd),
+ // and any BpfMap has (or should have...) well defined key/value types
+ // (or at least their sizes) so in practice it doesn't really matter.
+ private SingleWriterBpfMap(@NonNull final String path, final Class<K> key,
+ final Class<V> value) throws ErrnoException, NullPointerException {
+ super(path, BPF_F_RDWR_EXCLUSIVE, key, value);
+
+ // Populate cache with the current map contents.
+ K currentKey = super.getFirstKey();
+ while (currentKey != null) {
+ mCache.put(currentKey, super.getValue(currentKey));
+ currentKey = super.getNextKey(currentKey);
+ }
+ }
+
+ // This allows reuse of SingleWriterBpfMap objects for the same {path, keyClass, valueClass}.
+ // These are never destroyed, so once created the lock is (effectively) held till process death
+ // (even if fixed, there would still be a write-only fd cache in underlying BpfMap base class).
+ private static final HashMap<Pair<String, Pair<Class, Class>>, SingleWriterBpfMap>
+ singletonCache = new HashMap<>();
+
+ // This is the public 'factory method' that (creates if needed and) returns a singleton instance
+ // for a given map. This holds an exclusive lock and has a permanent write-through cache.
+ // It will not be released until process death (or at least unload of the relevant class loader)
+ public synchronized static <KK extends Struct, VV extends Struct> SingleWriterBpfMap<KK,VV>
+ getSingleton(@NonNull final String path, final Class<KK> key, final Class<VV> value)
+ throws ErrnoException, NullPointerException {
+ var cacheKey = new Pair<>(path, new Pair<Class,Class>(key, value));
+ if (!singletonCache.containsKey(cacheKey))
+ singletonCache.put(cacheKey, new SingleWriterBpfMap(path, key, value));
+ return singletonCache.get(cacheKey);
+ }
+
+ @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/SocketUtils.java b/staticlibs/device/com/android/net/module/util/SocketUtils.java
index 5e6a6c6..51671a6 100644
--- a/staticlibs/device/com/android/net/module/util/SocketUtils.java
+++ b/staticlibs/device/com/android/net/module/util/SocketUtils.java
@@ -19,8 +19,7 @@
import static android.net.util.SocketUtils.closeSocket;
import android.annotation.NonNull;
-import android.annotation.RequiresApi;
-import android.os.Build;
+import android.annotation.SuppressLint;
import android.system.NetlinkSocketAddress;
import java.io.FileDescriptor;
@@ -41,7 +40,11 @@
/**
* Make a socket address to communicate with netlink.
*/
- @NonNull @RequiresApi(Build.VERSION_CODES.S)
+ // NetlinkSocketAddress was CorePlatformApi on R and linter warns this is available on S+.
+ // android.net.util.SocketUtils.makeNetlinkSocketAddress can be used instead, but this method
+ // has been used on R, so suppress the linter and keep as it is.
+ @SuppressLint("NewApi")
+ @NonNull
public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) {
return new NetlinkSocketAddress(portId, groupsMask);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
similarity index 96%
rename from Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
rename to staticlibs/device/com/android/net/module/util/SyncStateMachine.java
index a17eb26..da184d3 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
+++ b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.networkstack.tethering.util;
+package com.android.net.module.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -165,6 +165,11 @@
* The message is processed sequentially, so calling this method recursively is not permitted.
* In other words, using this method inside State#enter, State#exit, or State#processMessage
* is incorrect and will result in an IllegalStateException.
+ *
+ * @param what is assigned to Message.what
+ * @param arg1 is assigned to Message.arg1
+ * @param arg2 is assigned to Message.arg2
+ * @param obj is assigned to Message.obj
*/
public final void processMessage(int what, int arg1, int arg2, @Nullable Object obj) {
ensureCorrectThread();
@@ -189,6 +194,15 @@
mCurrentlyProcessing = Integer.MIN_VALUE;
}
+ /**
+ * Synchronously process a message and perform state transition.
+ *
+ * @param what is assigned to Message.what.
+ */
+ public final void processMessage(int what) {
+ processMessage(what, 0, 0, null);
+ }
+
private void maybeProcessSelfMessageQueue() {
while (!mSelfMsgQueue.isEmpty()) {
currentStateProcessMessageThenPerformTransitions(mSelfMsgQueue.poll());
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/NdOption.java b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java
index defc88a..4f58380 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NdOption.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java
@@ -67,6 +67,9 @@
case StructNdOptRdnss.TYPE:
return StructNdOptRdnss.parse(buf);
+ case StructNdOptPio.TYPE:
+ return StructNdOptPio.parse(buf);
+
default:
int newPosition = Math.min(buf.limit(), buf.position() + length * 8);
buf.position(newPosition);
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/StructNdOptPio.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPio.java
new file mode 100644
index 0000000..65541eb
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPio.java
@@ -0,0 +1,120 @@
+/*
+ * 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 android.net.IpPrefix;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.HexDump;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.PrefixInformationOption;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * The Prefix Information Option. RFC 4861.
+ *
+ * 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 | Length | Prefix Length |L|A|R|P| Rsvd1 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Valid Lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Preferred Lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Reserved2 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | |
+ * + +
+ * | |
+ * + Prefix +
+ * | |
+ * + +
+ * | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class StructNdOptPio extends NdOption {
+ private static final String TAG = StructNdOptPio.class.getSimpleName();
+ public static final int TYPE = 3;
+ public static final byte LENGTH = 4; // Length in 8-byte units
+
+ public final byte flags;
+ public final long preferred;
+ public final long valid;
+ @NonNull
+ public final IpPrefix prefix;
+
+ public StructNdOptPio(byte flags, long preferred, long valid, @NonNull final IpPrefix prefix) {
+ super((byte) TYPE, LENGTH);
+ this.prefix = Objects.requireNonNull(prefix, "prefix must not be null");
+ this.flags = flags;
+ this.preferred = preferred;
+ this.valid = valid;
+ }
+
+ /**
+ * Parses a PrefixInformation option from a {@link ByteBuffer}.
+ *
+ * @param buf The buffer from which to parse the option. The buffer's byte order must be
+ * {@link java.nio.ByteOrder#BIG_ENDIAN}.
+ * @return the parsed option, or {@code null} if the option could not be parsed successfully.
+ */
+ public static StructNdOptPio parse(@NonNull ByteBuffer buf) {
+ if (buf == null || buf.remaining() < LENGTH * 8) return null;
+ try {
+ final PrefixInformationOption pio = Struct.parse(PrefixInformationOption.class, buf);
+ if (pio.type != TYPE) {
+ throw new IllegalArgumentException("Invalid type " + pio.type);
+ }
+ if (pio.length != LENGTH) {
+ throw new IllegalArgumentException("Invalid length " + pio.length);
+ }
+ return new StructNdOptPio(pio.flags, pio.preferredLifetime, pio.validLifetime,
+ pio.getIpPrefix());
+ } catch (IllegalArgumentException | BufferUnderflowException e) {
+ // Not great, but better than throwing an exception that might crash the caller.
+ // Convention in this package is that null indicates that the option was truncated
+ // or malformed, so callers must already handle it.
+ Log.d(TAG, "Invalid PIO option: " + e);
+ return null;
+ }
+ }
+
+ protected void writeToByteBuffer(ByteBuffer buf) {
+ buf.put(PrefixInformationOption.build(prefix, flags, valid, preferred));
+ }
+
+ /** Outputs the wire format of the option to a new big-endian ByteBuffer. */
+ public ByteBuffer toByteBuffer() {
+ final ByteBuffer buf = ByteBuffer.allocate(Struct.getSize(PrefixInformationOption.class));
+ writeToByteBuffer(buf);
+ buf.flip();
+ return buf;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return String.format("NdOptPio(flags:%s, preferred lft:%s, valid lft:%s, prefix:%s)",
+ HexDump.toHexString(flags), preferred, valid, prefix);
+ }
+}
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/device/com/android/net/module/util/structs/FragmentHeader.java b/staticlibs/device/com/android/net/module/util/structs/FragmentHeader.java
new file mode 100644
index 0000000..3da6a38
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/FragmentHeader.java
@@ -0,0 +1,56 @@
+/*
+ * 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.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * IPv6 Fragment Extension header, as per https://tools.ietf.org/html/rfc2460.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Next Header | Reserved | Fragment Offset |Res|M|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Identification |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class FragmentHeader extends Struct {
+ @Field(order = 0, type = Type.U8)
+ public final short nextHeader;
+ @Field(order = 1, type = Type.S8)
+ public final byte reserved;
+ @Field(order = 2, type = Type.U16)
+ public final int fragmentOffset;
+ @Field(order = 3, type = Type.S32)
+ public final int identification;
+
+ public FragmentHeader(final short nextHeader, final byte reserved, final int fragmentOffset,
+ final int identification) {
+ this.nextHeader = nextHeader;
+ this.reserved = reserved;
+ this.fragmentOffset = fragmentOffset;
+ this.identification = identification;
+ }
+
+ public FragmentHeader(final short nextHeader, final int fragmentOffset,
+ final int identification) {
+ this(nextHeader, (byte) 0, fragmentOffset, identification);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
index 49d7654..bbbe571 100644
--- a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
@@ -21,11 +21,16 @@
import android.net.IpPrefix;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Computed;
import com.android.net.module.util.Struct.Field;
import com.android.net.module.util.Struct.Type;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -71,7 +76,11 @@
@Field(order = 7, type = Type.ByteArray, arraysize = 16)
public final byte[] prefix;
- PrefixInformationOption(final byte type, final byte length, final byte prefixLen,
+ @Computed
+ private final IpPrefix mIpPrefix;
+
+ @VisibleForTesting
+ public PrefixInformationOption(final byte type, final byte length, final byte prefixLen,
final byte flags, final long validLifetime, final long preferredLifetime,
final int reserved, @NonNull final byte[] prefix) {
this.type = type;
@@ -82,6 +91,23 @@
this.preferredLifetime = preferredLifetime;
this.reserved = reserved;
this.prefix = prefix;
+
+ try {
+ final Inet6Address addr = (Inet6Address) InetAddress.getByAddress(prefix);
+ mIpPrefix = new IpPrefix(addr, prefixLen);
+ } catch (UnknownHostException | ClassCastException e) {
+ // UnknownHostException should never happen unless prefix is null.
+ // ClassCastException can occur when prefix is an IPv6 mapped IPv4 address.
+ // Both scenarios should throw an exception in the context of Struct#parse().
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Return the prefix {@link IpPrefix} included in the PIO.
+ */
+ public IpPrefix getIpPrefix() {
+ return mIpPrefix;
}
/**
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 54ce01e..7066131 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
@@ -39,12 +39,13 @@
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_SATELLITE;
import static android.net.NetworkCapabilities.TRANSPORT_USB;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
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;
@@ -75,8 +76,8 @@
TRANSPORT_BLUETOOTH,
TRANSPORT_WIFI,
TRANSPORT_ETHERNET,
- TRANSPORT_USB
-
+ TRANSPORT_USB,
+ TRANSPORT_SATELLITE
// Notably, TRANSPORT_TEST is not in this list as any network that has TRANSPORT_TEST and
// one of the above transports should be counted as that transport, to keep tests as
// realistic as possible.
@@ -88,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.
@@ -158,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..a8c50d8 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -129,7 +129,9 @@
public static final int IPV6_PROTOCOL_OFFSET = 6;
public static final int IPV6_SRC_ADDR_OFFSET = 8;
public static final int IPV6_DST_ADDR_OFFSET = 24;
+ public static final int IPV6_FRAGMENT_ID_OFFSET = 4;
public static final int IPV6_MIN_MTU = 1280;
+ public static final int IPV6_FRAGMENT_ID_LEN = 4;
public static final int IPV6_FRAGMENT_HEADER_LEN = 8;
public static final int RFC7421_PREFIX_LENGTH = 64;
// getSockOpt() for v6 MTU
@@ -141,6 +143,8 @@
public static final Inet6Address IPV6_ADDR_ALL_HOSTS_MULTICAST =
(Inet6Address) InetAddresses.parseNumericAddress("ff02::3");
+ public static final int IPPROTO_FRAGMENT = 44;
+
/**
* ICMP constants.
*
@@ -179,6 +183,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/lint-baseline.xml b/staticlibs/lint-baseline.xml
deleted file mode 100644
index 2ee3a43..0000000
--- a/staticlibs/lint-baseline.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha04" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha04">
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `makeNetlinkSocketAddress`"
- errorLine1=" Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java"
- line="111"
- column="25"/>
- </issue>
-
-</issues>
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
index d716358..cd51004 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -151,7 +151,7 @@
inline base::Result<void> BpfRingbufBase::Init(const char* path) {
- mRingFd.reset(mapRetrieveRW(path));
+ mRingFd.reset(mapRetrieveExclusiveRW(path));
if (!mRingFd.ok()) {
return android::base::ErrnoError()
<< "failed to retrieve ringbuffer at " << path;
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_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index baff09b..4ddec8b 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -37,7 +37,30 @@
#define BPFLOADER_IGNORED_ON_VERSION 33u
// Android U / 14 (api level 34) - various new program types added
-#define BPFLOADER_U_VERSION 37u
+#define BPFLOADER_U_VERSION 38u
+
+// Android U QPR2 / 14 (api level 34) - platform only
+// (note: the platform bpfloader in V isn't really versioned at all,
+// as there is no need as it can only load objects compiled at the
+// same time as itself and the rest of the platform)
+#define BPFLOADER_U_QPR2_VERSION 41u
+#define BPFLOADER_PLATFORM_VERSION BPFLOADER_U_QPR2_VERSION
+
+// Android Mainline - this bpfloader should eventually go back to T (or even S)
+// Note: this value (and the following +1u's) are hardcoded in NetBpfLoad.cpp
+#define BPFLOADER_MAINLINE_VERSION 42u
+
+// Android Mainline BpfLoader when running on Android T
+#define BPFLOADER_MAINLINE_T_VERSION (BPFLOADER_MAINLINE_VERSION + 1u)
+
+// Android Mainline BpfLoader when running on Android U
+#define BPFLOADER_MAINLINE_U_VERSION (BPFLOADER_MAINLINE_T_VERSION + 1u)
+
+// Android Mainline BpfLoader when running on Android U QPR3
+#define BPFLOADER_MAINLINE_U_QPR3_VERSION (BPFLOADER_MAINLINE_U_VERSION + 1u)
+
+// Android Mainline BpfLoader when running on Android V
+#define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_QPR3_VERSION + 1u)
/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
* before #include "bpf_helpers.h" to change which bpfloaders will
@@ -48,7 +71,7 @@
* In which case it's just best to use the default.
*/
#ifndef BPFLOADER_MIN_VER
-#define BPFLOADER_MIN_VER COMPILE_FOR_BPFLOADER_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_PLATFORM_VERSION
#endif
#ifndef BPFLOADER_MAX_VER
@@ -111,10 +134,13 @@
#define KVER_NONE KVER_(0)
#define KVER_4_14 KVER(4, 14, 0)
#define KVER_4_19 KVER(4, 19, 0)
-#define KVER_5_4 KVER(5, 4, 0)
-#define KVER_5_8 KVER(5, 8, 0)
-#define KVER_5_9 KVER(5, 9, 0)
+#define KVER_5_4 KVER(5, 4, 0)
+#define KVER_5_8 KVER(5, 8, 0)
+#define KVER_5_9 KVER(5, 9, 0)
+#define KVER_5_10 KVER(5, 10, 0)
#define KVER_5_15 KVER(5, 15, 0)
+#define KVER_6_1 KVER(6, 1, 0)
+#define KVER_6_6 KVER(6, 6, 0)
#define KVER_INF KVER_(0xFFFFFFFFu)
#define KVER_IS_AT_LEAST(kver, a, b, c) ((kver).kver >= KVER(a, b, c).kver)
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
index ef03c4d..00ef91a 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
@@ -48,9 +48,6 @@
#define DEFAULT_SIZEOF_BPF_MAP_DEF 32 // v0.0 struct: enum (uint sized) + 7 uint
#define DEFAULT_SIZEOF_BPF_PROG_DEF 20 // v0.0 struct: 4 uint + bool + 3 byte alignment pad
-// By default, unless otherwise specified, allow the use of features only supported by v0.37.
-#define COMPILE_FOR_BPFLOADER_VERSION 37u
-
/*
* The bpf_{map,prog}_def structures are compiled for different architectures.
* Once by the BPF compiler for the BPF architecture, and once by a C++
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index 9995cb9..73cef89 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -16,8 +16,11 @@
#pragma once
+#include <stdlib.h>
+#include <unistd.h>
#include <linux/bpf.h>
#include <linux/unistd.h>
+#include <sys/file.h>
#ifdef BPF_FD_JUST_USE_INT
#define BPF_FD_TYPE int
@@ -128,26 +131,74 @@
});
}
-inline int mapRetrieve(const char* pathname, uint32_t flag) {
- return bpfFdGet(pathname, flag);
+int bpfGetFdMapId(const BPF_FD_TYPE map_fd);
+
+inline int bpfLock(int fd, short type) {
+ if (fd < 0) return fd; // pass any errors straight through
+#ifdef BPF_MAP_LOCKLESS_FOR_TEST
+ return fd;
+#endif
+#ifdef BPF_FD_JUST_USE_INT
+ int mapId = bpfGetFdMapId(fd);
+ int saved_errno = errno;
+#else
+ base::unique_fd ufd(fd);
+ int mapId = bpfGetFdMapId(ufd);
+ int saved_errno = errno;
+ (void)ufd.release();
+#endif
+ // 4.14+ required to fetch map id, but we don't want to call isAtLeastKernelVersion
+ if (mapId == -1 && saved_errno == EINVAL) return fd;
+ if (mapId <= 0) abort(); // should not be possible
+
+ // on __LP64__ (aka. 64-bit userspace) 'struct flock64' is the same as 'struct flock'
+ struct flock64 fl = {
+ .l_type = type, // short: F_{RD,WR,UN}LCK
+ .l_whence = SEEK_SET, // short: SEEK_{SET,CUR,END}
+ .l_start = mapId, // off_t: start offset
+ .l_len = 1, // off_t: number of bytes
+ };
+
+ // see: bionic/libc/bionic/fcntl.cpp: iff !__LP64__ this uses fcntl64
+ int ret = fcntl(fd, F_OFD_SETLK, &fl);
+ if (!ret) return fd; // success
+ close(fd);
+ return ret; // most likely -1 with errno == EAGAIN, due to already held lock
+}
+
+inline int mapRetrieveLocklessRW(const char* pathname) {
+ return bpfFdGet(pathname, 0);
+}
+
+inline int mapRetrieveExclusiveRW(const char* pathname) {
+ return bpfLock(mapRetrieveLocklessRW(pathname), F_WRLCK);
}
inline int mapRetrieveRW(const char* pathname) {
- return mapRetrieve(pathname, 0);
+ return bpfLock(mapRetrieveLocklessRW(pathname), F_RDLCK);
}
inline int mapRetrieveRO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_RDONLY);
+ return bpfFdGet(pathname, BPF_F_RDONLY);
}
+// WARNING: it's impossible to grab a shared (ie. read) lock on a write-only fd,
+// so we instead choose to grab an exclusive (ie. write) lock.
inline int mapRetrieveWO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_WRONLY);
+ return bpfLock(bpfFdGet(pathname, BPF_F_WRONLY), F_WRLCK);
}
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_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
index b92f107..1923ceb 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -35,7 +35,24 @@
jstring path, jint mode, jint keySize, jint valueSize) {
ScopedUtfChars pathname(env, path);
- jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast<unsigned>(mode));
+ jint fd = -1;
+ switch (mode) {
+ case 0:
+ fd = bpf::mapRetrieveRW(pathname.c_str());
+ break;
+ case BPF_F_RDONLY:
+ fd = bpf::mapRetrieveRO(pathname.c_str());
+ break;
+ case BPF_F_WRONLY:
+ fd = bpf::mapRetrieveWO(pathname.c_str());
+ break;
+ case BPF_F_RDONLY|BPF_F_WRONLY:
+ fd = bpf::mapRetrieveExclusiveRW(pathname.c_str());
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
if (fd < 0) {
jniThrowErrnoException(env, "nativeBpfFdGet", errno);
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/Android.bp b/staticlibs/tests/unit/Android.bp
index 4c226cc..cf67a82 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -25,6 +25,7 @@
"net-utils-device-common-async",
"net-utils-device-common-bpf",
"net-utils-device-common-ip",
+ "net-utils-device-common-struct-base",
"net-utils-device-common-wear",
],
libs: [
@@ -38,6 +39,7 @@
"//packages/modules/NetworkStack/tests/integration",
],
lint: {
+ strict_updatability_linting: true,
test: true,
},
}
@@ -55,4 +57,7 @@
],
jarjar_rules: "jarjar-rules.txt",
test_suites: ["device-tests"],
+ lint: {
+ strict_updatability_linting: true,
+ },
}
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/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index f32337d..9fb61d9 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -17,6 +17,7 @@
package com.android.net.module.util;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_CAPTIVEPORTALLOGIN;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
@@ -233,8 +234,12 @@
TEST_EXPERIMENT_FLAG));
doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
+ doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(
+ NAMESPACE_CAPTIVEPORTALLOGIN, TEST_EXPERIMENT_FLAG));
assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+ assertTrue(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
}
@Test
public void testIsFeatureEnabledFeatureDefaultDisabled() throws Exception {
@@ -242,8 +247,12 @@
TEST_EXPERIMENT_FLAG));
doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
+ doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_CAPTIVEPORTALLOGIN,
+ TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+ assertFalse(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
// If the flag is unset, package info is not queried
verify(mContext, never()).getPackageManager();
@@ -257,8 +266,12 @@
TEST_EXPERIMENT_FLAG));
doReturn("1").when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
+ doReturn("1").when(() -> DeviceConfig.getProperty(NAMESPACE_CAPTIVEPORTALLOGIN,
+ TEST_EXPERIMENT_FLAG));
assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+ assertTrue(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
// If the feature is force enabled, package info is not queried
verify(mContext, never()).getPackageManager();
@@ -272,8 +285,12 @@
TEST_EXPERIMENT_FLAG));
doReturn("-1").when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
+ doReturn("-1").when(() -> DeviceConfig.getProperty(NAMESPACE_CAPTIVEPORTALLOGIN,
+ TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+ assertFalse(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
// If the feature is force disabled, package info is not queried
verify(mContext, never()).getPackageManager();
@@ -290,24 +307,36 @@
TEST_EXPERIMENT_FLAG));
doReturn("1").when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
+ doReturn("1").when(() -> DeviceConfig.getProperty(NAMESPACE_CAPTIVEPORTALLOGIN,
+ TEST_EXPERIMENT_FLAG));
assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+ assertTrue(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
// Feature should be disabled by flag value "999999999".
doReturn("999999999").when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY,
TEST_EXPERIMENT_FLAG));
doReturn("999999999").when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
+ doReturn("999999999").when(() -> DeviceConfig.getProperty(NAMESPACE_CAPTIVEPORTALLOGIN,
+ TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+ assertFalse(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
// If the flag is not set feature is disabled
doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY,
TEST_EXPERIMENT_FLAG));
doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
+ doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_CAPTIVEPORTALLOGIN,
+ TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+ assertFalse(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
}
@Test
@@ -320,9 +349,13 @@
NAMESPACE_CONNECTIVITY, TEST_EXPERIMENT_FLAG));
doReturn("0").when(() -> DeviceConfig.getProperty(
NAMESPACE_TETHERING, TEST_EXPERIMENT_FLAG));
+ doReturn("0").when(() -> DeviceConfig.getProperty(
+ NAMESPACE_CAPTIVEPORTALLOGIN, TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+ assertFalse(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
@@ -343,6 +376,21 @@
}
@Test
+ public void testIsCaptivePortalLoginFeatureEnabledCaching() throws Exception {
+ doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(
+ NAMESPACE_CAPTIVEPORTALLOGIN, TEST_EXPERIMENT_FLAG));
+ assertTrue(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
+ assertTrue(DeviceConfigUtils.isCaptivePortalLoginFeatureEnabled(mContext,
+ TEST_EXPERIMENT_FLAG));
+
+ // Package info is only queried once
+ verify(mContext, times(1)).getPackageManager();
+ verify(mContext, times(1)).getPackageName();
+ verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+ }
+
+ @Test
public void testIsTetheringFeatureEnabledCaching() throws Exception {
doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
TEST_EXPERIMENT_FLAG));
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
index e40cd6b..886336c 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
@@ -21,9 +21,13 @@
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_ID_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_ID_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
import static com.android.net.module.util.NetworkStackConstants.TCP_HEADER_MIN_LEN;
import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
@@ -54,6 +58,8 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -489,10 +495,103 @@
(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
};
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_NO_FRAG =
+ new byte[] {
+ // packet = Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", type='IPv6')/
+ // IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, fl=0x515ca,
+ // hlim=0x40)/UDP(sport=9876, dport=433)/
+ // Raw([i%256 for i in range(0, 500)]);
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x01, (byte) 0xfc, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x01, (byte) 0xfc, (byte) 0xd3, (byte) 0x9e,
+ // Data
+ // 500 bytes of repeated 0x00~0xff
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG1 =
+ new byte[] {
+ // packet = Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", type='IPv6')/
+ // IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, fl=0x515ca,
+ // hlim=0x40)/UDP(sport=9876, dport=433)/
+ // Raw([i%256 for i in range(0, 500)]);
+ // packets=fragment6(packet, 400);
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x01, (byte) 0x58, (byte) 0x2c, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // Fragement Header
+ (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x01, (byte) 0xfc, (byte) 0xd3, (byte) 0x9e,
+ // Data
+ // 328 bytes of repeated 0x00~0xff, start:0x00 end:0x47
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG2 =
+ new byte[] {
+ // packet = Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", type='IPv6')/
+ // IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, fl=0x515ca,
+ // hlim=0x40)/UDP(sport=9876, dport=433)/
+ // Raw([i%256 for i in range(0, 500)]);
+ // packets=fragment6(packet, 400);
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0xb4, (byte) 0x2c, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // Fragement Header
+ (byte) 0x11, (byte) 0x00, (byte) 0x01, (byte) 0x50,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // Data
+ // 172 bytes of repeated 0x00~0xff, start:0x48 end:0xf3
+ };
+
/**
* Build a packet which has ether header, IP header, TCP/UDP header and data.
* The ethernet header and data are optional. Note that both source mac address and
- * destination mac address are required for ethernet header.
+ * destination mac address are required for ethernet header. The packet will be fragmented into
+ * multiple smaller packets if the packet size exceeds L2 mtu.
*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 2 header (EthernetHeader) | (optional)
@@ -511,11 +610,12 @@
* @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
* currently supported.
* @param payload the payload.
+ * @param l2mtu the Link MTU. It's the upper bound of each packet size. Zero means no limit.
*/
@NonNull
- private ByteBuffer buildPacket(@Nullable final MacAddress srcMac,
+ private List<ByteBuffer> buildPackets(@Nullable final MacAddress srcMac,
@Nullable final MacAddress dstMac, final int l3proto, final int l4proto,
- @Nullable final ByteBuffer payload)
+ @Nullable final ByteBuffer payload, int l2mtu)
throws Exception {
if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
fail("Unsupported layer 3 protocol " + l3proto);
@@ -562,7 +662,15 @@
payload.clear();
}
- return packetBuilder.finalizePacket();
+ return packetBuilder.finalizePacket(l2mtu > 0 ? l2mtu : Integer.MAX_VALUE);
+ }
+
+ @NonNull
+ private ByteBuffer buildPacket(@Nullable final MacAddress srcMac,
+ @Nullable final MacAddress dstMac, final int l3proto, final int l4proto,
+ @Nullable final ByteBuffer payload)
+ throws Exception {
+ return buildPackets(srcMac, dstMac, l3proto, l4proto, payload, 0).get(0);
}
/**
@@ -874,6 +982,66 @@
assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR_DATA, packet.array());
}
+ private void checkIpv6PacketIgnoreFragmentId(byte[] expected, byte[] actual) {
+ final int offset = ETHER_HEADER_LEN + IPV6_HEADER_LEN + IPV6_FRAGMENT_ID_OFFSET;
+ assertArrayEquals(Arrays.copyOf(expected, offset), Arrays.copyOf(actual, offset));
+ assertArrayEquals(
+ Arrays.copyOfRange(expected, offset + IPV6_FRAGMENT_ID_LEN, expected.length),
+ Arrays.copyOfRange(actual, offset + IPV6_FRAGMENT_ID_LEN, actual.length));
+ }
+
+ @Test
+ public void testBuildPacketIPv6FragmentUdpData() throws Exception {
+ // A UDP packet with 500 bytes payload will be fragmented into two UDP packets each carrying
+ // 328 and 172 bytes of payload if the Link MTU is 400. Note that only the first packet
+ // contains the original UDP header.
+ final int payloadLen = 500;
+ final int payloadLen1 = 328;
+ final int payloadLen2 = 172;
+ final int l2mtu = 400;
+ final byte[] payload = new byte[payloadLen];
+ // Initialize the payload with repeated values from 0x00 to 0xff.
+ for (int i = 0; i < payload.length; i++) {
+ payload[i] = (byte) (i & 0xff);
+ }
+
+ // Verify original UDP packet.
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ ByteBuffer.wrap(payload));
+ final int headerLen = TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_NO_FRAG.length;
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_NO_FRAG,
+ Arrays.copyOf(packet.array(), headerLen));
+ assertArrayEquals(payload,
+ Arrays.copyOfRange(packet.array(), headerLen, headerLen + payloadLen));
+
+ // Verify fragments of UDP packet.
+ final List<ByteBuffer> packets = buildPackets(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ ByteBuffer.wrap(payload), l2mtu);
+ assertEquals(2, packets.size());
+
+ // Verify first fragment.
+ int headerLen1 = TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG1.length;
+ // (1) Compare packet content up to the UDP header, excluding the fragment ID as it's a
+ // random value.
+ checkIpv6PacketIgnoreFragmentId(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG1,
+ Arrays.copyOf(packets.get(0).array(), headerLen1));
+ // (2) Compare UDP payload.
+ assertArrayEquals(Arrays.copyOf(payload, payloadLen1),
+ Arrays.copyOfRange(packets.get(0).array(), headerLen1, headerLen1 + payloadLen1));
+
+ // Verify second fragment (similar to the first one).
+ int headerLen2 = TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG2.length;
+ checkIpv6PacketIgnoreFragmentId(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG2,
+ Arrays.copyOf(packets.get(1).array(), headerLen2));
+ assertArrayEquals(Arrays.copyOfRange(payload, payloadLen1, payloadLen1 + payloadLen2),
+ Arrays.copyOfRange(packets.get(1).array(), headerLen2, headerLen2 + payloadLen2));
+ // Verify that the fragment IDs in the first and second fragments are the same.
+ final int offset = ETHER_HEADER_LEN + IPV6_HEADER_LEN + IPV6_FRAGMENT_ID_OFFSET;
+ assertArrayEquals(
+ Arrays.copyOfRange(packets.get(0).array(), offset, offset + IPV6_FRAGMENT_ID_LEN),
+ Arrays.copyOfRange(packets.get(1).array(), offset, offset + IPV6_FRAGMENT_ID_LEN));
+ }
+
@Test
public void testFinalizePacketWithoutIpv4Header() throws Exception {
final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/SyncStateMachineTest.kt
similarity index 98%
rename from Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt
rename to staticlibs/tests/unit/src/com/android/net/module/util/SyncStateMachineTest.kt
index 3a57fdd..d534054 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/SyncStateMachineTest.kt
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.networkstack.tethering.util
+package com.android.net.module.util
import android.os.Message
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.State
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo
+import com.android.net.module.util.SyncStateMachine.StateInfo
import java.util.ArrayDeque
import java.util.ArrayList
import kotlin.test.assertFailsWith
@@ -45,7 +45,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
-class SynStateMachineTest {
+class SyncStateMachineTest {
private val mState1 = spy(object : TestState(MSG_1) {})
private val mState2 = spy(object : TestState(MSG_2) {})
private val mState3 = spy(object : TestState(MSG_3) {})
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/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPioTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPioTest.java
new file mode 100644
index 0000000..0d88829
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPioTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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 static com.android.net.module.util.NetworkStackConstants.INFINITE_LEASE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.net.IpPrefix;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.structs.PrefixInformationOption;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructNdOptPioTest {
+ private static final IpPrefix TEST_PREFIX = new IpPrefix("2a00:79e1:abc:f605::/64");
+ private static final byte TEST_PIO_FLAGS_P_UNSET = (byte) 0xC0; // L=1,A=1
+ private static final byte TEST_PIO_FLAGS_P_SET = (byte) 0xD0; // L=1,A=1,P=1
+ private static final String PIO_BYTES =
+ "0304" // type=3, length=4
+ + "40" // prefix length=64
+ + "C0" // L=1,A=1
+ + "00278D00" // valid=259200
+ + "00093A80" // preferred=604800
+ + "00000000" // Reserved2
+ + "2A0079E10ABCF6050000000000000000"; // prefix=2a00:79e1:abc:f605::
+
+ private static final String PIO_WITH_P_FLAG_BYTES =
+ "0304" // type=3, length=4
+ + "40" // prefix length=64
+ + "D0" // L=1,A=1,P=1
+ + "00278D00" // valid=2592000
+ + "00093A80" // preferred=604800
+ + "00000000" // Reserved2
+ + "2A0079E10ABCF6050000000000000000"; // prefix=2a00:79e1:abc:f605::
+
+ private static final String PIO_WITH_P_FLAG_INFINITY_LIFETIME_BYTES =
+ "0304" // type=3, length=4
+ + "40" // prefix length=64
+ + "D0" // L=1,A=1,P=1
+ + "FFFFFFFF" // valid=infinity
+ + "FFFFFFFF" // preferred=infintiy
+ + "00000000" // Reserved2
+ + "2A0079E10ABCF6050000000000000000"; // prefix=2a00:79e1:abc:f605::
+
+ private static void assertPioOptMatches(final StructNdOptPio opt, int length, byte flags,
+ long preferred, long valid, final IpPrefix prefix) {
+ assertEquals(StructNdOptPio.TYPE, opt.type);
+ assertEquals(length, opt.length);
+ assertEquals(flags, opt.flags);
+ assertEquals(preferred, opt.preferred);
+ assertEquals(valid, opt.valid);
+ assertEquals(prefix, opt.prefix);
+ }
+
+ private static void assertToByteBufferMatches(final StructNdOptPio opt, final String expected) {
+ String actual = HexEncoding.encodeToString(opt.toByteBuffer().array());
+ assertEquals(expected, actual);
+ }
+
+ private static void doPioParsingTest(final String optionHexString, int length, byte flags,
+ long preferred, long valid, final IpPrefix prefix) {
+ final byte[] rawBytes = HexEncoding.decode(optionHexString);
+ final StructNdOptPio opt = StructNdOptPio.parse(ByteBuffer.wrap(rawBytes));
+ assertPioOptMatches(opt, length, flags, preferred, valid, prefix);
+ assertToByteBufferMatches(opt, optionHexString);
+ }
+
+ @Test
+ public void testParsingPioWithoutPFlag() {
+ doPioParsingTest(PIO_BYTES, 4 /* length */, TEST_PIO_FLAGS_P_UNSET,
+ 604800 /* preferred */, 2592000 /* valid */, TEST_PREFIX);
+ }
+
+ @Test
+ public void testParsingPioWithPFlag() {
+ doPioParsingTest(PIO_WITH_P_FLAG_BYTES, 4 /* length */, TEST_PIO_FLAGS_P_SET,
+ 604800 /* preferred */, 2592000 /* valid */, TEST_PREFIX);
+ }
+
+ @Test
+ public void testParsingPioWithPFlag_infinityLifetime() {
+ doPioParsingTest(PIO_WITH_P_FLAG_INFINITY_LIFETIME_BYTES, 4 /* length */,
+ TEST_PIO_FLAGS_P_SET,
+ Integer.toUnsignedLong(INFINITE_LEASE) /* preferred */,
+ Integer.toUnsignedLong(INFINITE_LEASE) /* valid */,
+ TEST_PREFIX);
+ }
+
+ @Test
+ public void testToByteBuffer() {
+ final StructNdOptPio pio =
+ new StructNdOptPio(TEST_PIO_FLAGS_P_UNSET, 604800 /* preferred */,
+ 2592000 /* valid */, TEST_PREFIX);
+ assertToByteBufferMatches(pio, PIO_BYTES);
+ }
+
+ @Test
+ public void testToByteBuffer_withPFlag() {
+ final StructNdOptPio pio =
+ new StructNdOptPio(TEST_PIO_FLAGS_P_SET, 604800 /* preferred */,
+ 2592000 /* valid */, TEST_PREFIX);
+ assertToByteBufferMatches(pio, PIO_WITH_P_FLAG_BYTES);
+ }
+
+ @Test
+ public void testToByteBuffer_infinityLifetime() {
+ final StructNdOptPio pio =
+ new StructNdOptPio(TEST_PIO_FLAGS_P_SET,
+ Integer.toUnsignedLong(INFINITE_LEASE) /* preferred */,
+ Integer.toUnsignedLong(INFINITE_LEASE) /* valid */, TEST_PREFIX);
+ assertToByteBufferMatches(pio, PIO_WITH_P_FLAG_INFINITY_LIFETIME_BYTES);
+ }
+
+ private static ByteBuffer makePioOption(byte type, byte length, byte prefixLen, byte flags,
+ long valid, long preferred, final byte[] prefix) {
+ final PrefixInformationOption pio = new PrefixInformationOption(type, length, prefixLen,
+ flags, valid, preferred, 0 /* reserved */, prefix);
+ return ByteBuffer.wrap(pio.writeToBytes(ByteOrder.BIG_ENDIAN));
+ }
+
+ @Test
+ public void testParsing_invalidOptionType() {
+ final ByteBuffer buf = makePioOption((byte) 24 /* wrong type:RIO */,
+ (byte) 4 /* length */, (byte) 64 /* prefixLen */, TEST_PIO_FLAGS_P_SET,
+ 2592000 /* valid */, 604800 /* preferred */, TEST_PREFIX.getRawAddress());
+ assertNull(StructNdOptPio.parse(buf));
+ }
+
+ @Test
+ public void testParsing_invalidOptionLength() {
+ final ByteBuffer buf = makePioOption((byte) 24 /* wrong type:RIO */,
+ (byte) 3 /* wrong length */, (byte) 64 /* prefixLen */,
+ TEST_PIO_FLAGS_P_SET, 2592000 /* valid */, 604800 /* preferred */,
+ TEST_PREFIX.getRawAddress());
+ assertNull(StructNdOptPio.parse(buf));
+ }
+
+ @Test
+ public void testParsing_truncatedByteBuffer() {
+ final ByteBuffer buf = makePioOption((byte) 3 /* type */, (byte) 4 /* length */,
+ (byte) 64 /* prefixLen */, TEST_PIO_FLAGS_P_SET,
+ 2592000 /* valid */, 604800 /* preferred */, TEST_PREFIX.getRawAddress());
+ final int len = buf.limit();
+ for (int i = 0; i < buf.limit() - 1; i++) {
+ buf.flip();
+ buf.limit(i);
+ assertNull("Option truncated to " + i + " bytes, should have returned null",
+ StructNdOptPio.parse(buf));
+ }
+ buf.flip();
+ buf.limit(len);
+
+ final StructNdOptPio opt = StructNdOptPio.parse(buf);
+ assertPioOptMatches(opt, (byte) 4 /* length */, TEST_PIO_FLAGS_P_SET,
+ 604800 /* preferred */, 2592000 /* valid */, TEST_PREFIX);
+ }
+
+ @Test
+ public void testParsing_invalidByteBufferLength() {
+ final ByteBuffer buf = makePioOption((byte) 3 /* type */, (byte) 4 /* length */,
+ (byte) 64 /* prefixLen */, TEST_PIO_FLAGS_P_SET,
+ 2592000 /* valid */, 604800 /* preferred */, TEST_PREFIX.getRawAddress());
+ buf.limit(31); // less than 4 * 8
+ assertNull(StructNdOptPio.parse(buf));
+ }
+
+ @Test
+ public void testToString() {
+ final ByteBuffer buf = makePioOption((byte) 3 /* type */, (byte) 4 /* length */,
+ (byte) 64 /* prefixLen */, TEST_PIO_FLAGS_P_SET,
+ 2592000 /* valid */, 604800 /* preferred */, TEST_PREFIX.getRawAddress());
+ final StructNdOptPio opt = StructNdOptPio.parse(buf);
+ final String expected = "NdOptPio"
+ + "(flags:D0, preferred lft:604800, valid lft:2592000,"
+ + " prefix:2a00:79e1:abc:f605::/64)";
+ assertEquals(expected, opt.toString());
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
index 4c3fde6..b5e3dff 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
@@ -69,11 +69,11 @@
}
@Test
- public void testGetValueAsIntger() {
+ public void testGetValueAsInteger() {
final StructNlAttr attr1 = new StructNlAttr(IFA_FLAGS, TEST_ADDR_FLAGS);
final Integer integer1 = attr1.getValueAsInteger();
final int int1 = attr1.getValueAsInt(0x08 /* default value */);
- assertEquals(integer1, new Integer(TEST_ADDR_FLAGS));
+ assertEquals(integer1, Integer.valueOf(TEST_ADDR_FLAGS));
assertEquals(int1, TEST_ADDR_FLAGS);
// Malformed attribute.
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/structs/FragmentHeaderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/structs/FragmentHeaderTest.java
new file mode 100644
index 0000000..1a78ca5
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/structs/FragmentHeaderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.structs;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class FragmentHeaderTest {
+ private static final byte[] HEADER_BYTES = new byte[] {
+ 17, /* nextHeader */
+ 0, /* reserved */
+ 15, 1, /* fragmentOffset */
+ 1, 2, 3, 4 /* identification */
+ };
+
+ @Test
+ public void testConstructor() {
+ FragmentHeader fragHdr = new FragmentHeader((short) 10 /* nextHeader */,
+ (byte) 11 /* reserved */,
+ 12 /* fragmentOffset */,
+ 13 /* identification */);
+
+ assertEquals(10, fragHdr.nextHeader);
+ assertEquals(11, fragHdr.reserved);
+ assertEquals(12, fragHdr.fragmentOffset);
+ assertEquals(13, fragHdr.identification);
+ }
+
+ @Test
+ public void testParseFragmentHeader() {
+ final ByteBuffer buf = ByteBuffer.wrap(HEADER_BYTES);
+ buf.order(ByteOrder.BIG_ENDIAN);
+ FragmentHeader fragHdr = FragmentHeader.parse(FragmentHeader.class, buf);
+
+ assertEquals(17, fragHdr.nextHeader);
+ assertEquals(0, fragHdr.reserved);
+ assertEquals(0xF01, fragHdr.fragmentOffset);
+ assertEquals(0x1020304, fragHdr.identification);
+ }
+
+ @Test
+ public void testWriteToBytes() {
+ FragmentHeader fragHdr = new FragmentHeader((short) 17 /* nextHeader */,
+ (byte) 0 /* reserved */,
+ 0xF01 /* fragmentOffset */,
+ 0x1020304 /* identification */);
+
+ byte[] bytes = fragHdr.writeToBytes(ByteOrder.BIG_ENDIAN);
+
+ assertArrayEquals("bytes = " + Arrays.toString(bytes), HEADER_BYTES, bytes);
+ }
+}
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index a8e5a69..3843b90 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -40,6 +40,7 @@
"net-utils-device-common-async",
"net-utils-device-common-netlink",
"net-utils-device-common-struct",
+ "net-utils-device-common-struct-base",
"net-utils-device-common-wear",
"modules-utils-build_system",
],
@@ -98,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/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index df6067d..e634f0e 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -34,17 +34,13 @@
private val connectUtil by lazy { ConnectUtil(context) }
@Test
- fun testCheckConnectivity() {
- checkWifiSetup()
- checkTelephonySetup()
- }
-
- private fun checkWifiSetup() {
+ fun testCheckWifiSetup() {
if (!pm.hasSystemFeature(FEATURE_WIFI)) return
connectUtil.ensureWifiValidated()
}
- private fun checkTelephonySetup() {
+ @Test
+ fun testCheckTelephonySetup() {
if (!pm.hasSystemFeature(FEATURE_TELEPHONY)) return
val tm = context.getSystemService(TelephonyManager::class.java)
?: fail("Could not get telephony service")
@@ -52,7 +48,7 @@
val commonError = "Check the test bench. To run the tests anyway for quick & dirty local " +
"testing, you can use atest X -- " +
"--test-arg com.android.testutils.ConnectivityTestTargetPreparer" +
- ":ignore-connectivity-check:true"
+ ":ignore-mobile-data-check:true"
// Do not use assertEquals: it outputs "expected X, was Y", which looks like a test failure
if (tm.simState == TelephonyManager.SIM_STATE_ABSENT) {
fail("The device has no SIM card inserted. $commonError")
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/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
similarity index 97%
rename from tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
rename to staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
index 5ba6c4c..8b88224 100644
--- a/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.net.cts
+package com.android.testutils
import android.net.DnsResolver
import android.net.Network
@@ -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/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..435fdd8 100644
--- a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
+++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -28,9 +28,11 @@
private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk"
private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer"
private const val CONNECTIVITY_CHECK_CLASS = "$CONNECTIVITY_PKG_NAME.ConnectivityCheckTest"
+
// As per the <instrumentation> defined in the checker manifest
private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner"
-private const val IGNORE_CONN_CHECK_OPTION = "ignore-connectivity-check"
+private const val IGNORE_WIFI_CHECK = "ignore-wifi-check"
+private const val IGNORE_MOBILE_DATA_CHECK = "ignore-mobile-data-check"
// The default updater package names, which might be updating packages while the CTS
// are running
@@ -41,14 +43,23 @@
*
* For quick and dirty local testing, the connectivity check can be disabled by running tests with
* "atest -- \
- * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-connectivity-check:true".
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-mobile-data-check:true". \
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-wifi-check:true".
*/
open class ConnectivityTestTargetPreparer : BaseTargetPreparer() {
private val installer = SuiteApkInstaller()
- @Option(name = IGNORE_CONN_CHECK_OPTION,
- description = "Disables the check for mobile data and wifi")
- private var ignoreConnectivityCheck = false
+ @Option(
+ name = IGNORE_WIFI_CHECK,
+ description = "Disables the check for wifi"
+ )
+ private var ignoreWifiCheck = false
+ @Option(
+ name = IGNORE_MOBILE_DATA_CHECK,
+ description = "Disables the check for mobile data"
+ )
+ private var ignoreMobileDataCheck = false
+
// The default value is never used, but false is a reasonable default
private var originalTestChainEnabled = false
private val originalUpdaterPkgsStatus = HashMap<String, Boolean>()
@@ -58,43 +69,62 @@
disableGmsUpdate(testInfo)
originalTestChainEnabled = getTestChainEnabled(testInfo)
originalUpdaterPkgsStatus.putAll(getUpdaterPkgsStatus(testInfo))
- setUpdaterNetworkingEnabled(testInfo, enableChain = true,
- enablePkgs = UPDATER_PKGS.associateWith { false })
- runPreparerApk(testInfo)
+ setUpdaterNetworkingEnabled(
+ testInfo,
+ enableChain = true,
+ enablePkgs = UPDATER_PKGS.associateWith { false }
+ )
+ runConnectivityCheckApk(testInfo)
+ refreshTime(testInfo)
}
- private fun runPreparerApk(testInfo: TestInformation) {
+ private fun runConnectivityCheckApk(testInfo: TestInformation) {
installer.setCleanApk(true)
installer.addTestFileName(CONNECTIVITY_CHECKER_APK)
installer.setShouldGrantPermission(true)
installer.setUp(testInfo)
+ val testMethods = mutableListOf<String>()
+ if (!ignoreWifiCheck) {
+ testMethods.add("testCheckWifiSetup")
+ }
+ if (!ignoreMobileDataCheck) {
+ testMethods.add("testCheckTelephonySetup")
+ }
+
+ testMethods.forEach {
+ runTestMethod(testInfo, it)
+ }
+ }
+
+ private fun runTestMethod(testInfo: TestInformation, method: String) {
val runner = DefaultRemoteAndroidTestRunner(
- CONNECTIVITY_PKG_NAME,
- CONNECTIVITY_CHECK_RUNNER_NAME,
- testInfo.device.iDevice)
+ CONNECTIVITY_PKG_NAME,
+ CONNECTIVITY_CHECK_RUNNER_NAME,
+ testInfo.device.iDevice
+ )
runner.runOptions = "--no-hidden-api-checks"
+ runner.setMethodName(CONNECTIVITY_CHECK_CLASS, method)
val receiver = CollectingTestListener()
if (!testInfo.device.runInstrumentationTests(runner, receiver)) {
- throw TargetSetupError("Device state check failed to complete",
- testInfo.device.deviceDescriptor)
+ throw TargetSetupError(
+ "Device state check failed to complete",
+ testInfo.device.deviceDescriptor
+ )
}
val runResult = receiver.currentRunResults
if (runResult.isRunFailure) {
- throw TargetSetupError("Failed to check device state before the test: " +
- runResult.runFailureMessage, testInfo.device.deviceDescriptor)
- }
-
- val ignoredTestClasses = mutableSetOf<String>()
- if (ignoreConnectivityCheck) {
- ignoredTestClasses.add(CONNECTIVITY_CHECK_CLASS)
+ throw TargetSetupError(
+ "Failed to check device state before the test: " +
+ runResult.runFailureMessage,
+ testInfo.device.deviceDescriptor
+ )
}
val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) ->
- if (TestResult.TestStatus.FAILURE != testResult.status ||
- ignoredTestClasses.contains(testDescription.className)) {
+ if (TestResult.TestStatus.FAILURE != testResult.status) {
null
} else {
"$testDescription: ${testResult.stackTrace}"
@@ -102,21 +132,27 @@
}.joinToString("\n")
if (errorMsg.isBlank()) return
- throw TargetSetupError("Device setup checks failed. Check the test bench: \n$errorMsg",
- testInfo.device.deviceDescriptor)
+ throw TargetSetupError(
+ "Device setup checks failed. Check the test bench: \n$errorMsg",
+ testInfo.device.deviceDescriptor
+ )
}
private fun disableGmsUpdate(testInfo: TestInformation) {
// This will be a no-op on devices without root (su) or not using gservices, but that's OK.
- testInfo.exec("su 0 am broadcast " +
+ testInfo.exec(
+ "su 0 am broadcast " +
"-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
- "-e finsky.play_services_auto_update_enabled false")
+ "-e finsky.play_services_auto_update_enabled false"
+ )
}
private fun clearGmsUpdateOverride(testInfo: TestInformation) {
- testInfo.exec("su 0 am broadcast " +
+ testInfo.exec(
+ "su 0 am broadcast " +
"-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
- "--esn finsky.play_services_auto_update_enabled")
+ "--esn finsky.play_services_auto_update_enabled"
+ )
}
private fun setUpdaterNetworkingEnabled(
@@ -136,17 +172,27 @@
testInfo.exec("cmd connectivity get-chain3-enabled").contains("chain:enabled")
private fun getUpdaterPkgsStatus(testInfo: TestInformation) =
- UPDATER_PKGS.associateWith { pkg ->
- !testInfo.exec("cmd connectivity get-package-networking-enabled $pkg")
- .contains(":deny")
- }
+ UPDATER_PKGS.associateWith { pkg ->
+ !testInfo.exec("cmd connectivity get-package-networking-enabled $pkg")
+ .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)
- setUpdaterNetworkingEnabled(testInfo,
- enableChain = originalTestChainEnabled,
- enablePkgs = originalUpdaterPkgsStatus)
+ setUpdaterNetworkingEnabled(
+ testInfo,
+ enableChain = originalTestChainEnabled,
+ enablePkgs = originalUpdaterPkgsStatus
+ )
clearGmsUpdateOverride(testInfo)
}
}
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt b/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
index af4f96d..c6e5f25 100644
--- a/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
@@ -19,10 +19,77 @@
package com.android.testutils
import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
import kotlin.system.measureTimeMillis
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
// For Java usage
fun durationOf(fn: Runnable) = measureTimeMillis { fn.run() }
fun CountDownLatch.await(timeoutMs: Long): Boolean = await(timeoutMs, TimeUnit.MILLISECONDS)
+
+/**
+ * Quit resources provided as a list by a supplier.
+ *
+ * The supplier may return more resources as the process progresses, for example while interrupting
+ * threads and waiting for them to finish they may spawn more threads, so this implements a
+ * [maxRetryCount] which, in this case, would be the maximum length of the thread chain that can be
+ * terminated.
+ */
+fun <T> quitResources(
+ maxRetryCount: Int,
+ supplier: () -> List<T>,
+ terminator: Consumer<T>
+) {
+ // Run it multiple times since new threads might be generated in a thread
+ // that is about to be terminated
+ for (retryCount in 0 until maxRetryCount) {
+ val resourcesToBeCleared = supplier()
+ if (resourcesToBeCleared.isEmpty()) return
+ for (resource in resourcesToBeCleared) {
+ terminator.accept(resource)
+ }
+ }
+ assertEmpty(supplier())
+}
+
+/**
+ * Implementation of [quitResources] to interrupt and wait for [ExecutorService]s to finish.
+ */
+@JvmOverloads
+fun quitExecutorServices(
+ maxRetryCount: Int,
+ interrupt: Boolean = true,
+ timeoutMs: Long = 10_000L,
+ supplier: () -> List<ExecutorService>
+) {
+ quitResources(maxRetryCount, supplier) { ecs ->
+ if (interrupt) {
+ ecs.shutdownNow()
+ }
+ assertTrue(ecs.awaitTermination(timeoutMs, TimeUnit.MILLISECONDS),
+ "ExecutorServices did not terminate within timeout")
+ }
+}
+
+/**
+ * Implementation of [quitResources] to interrupt and wait for [Thread]s to finish.
+ */
+@JvmOverloads
+fun quitThreads(
+ maxRetryCount: Int,
+ interrupt: Boolean = true,
+ timeoutMs: Long = 10_000L,
+ supplier: () -> List<Thread>
+) {
+ quitResources(maxRetryCount, supplier) { th ->
+ if (interrupt) {
+ th.interrupt()
+ }
+ th.join(timeoutMs)
+ assertFalse(th.isAlive, "Threads did not terminate within timeout.")
+ }
+}
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/OWNERS b/tests/cts/OWNERS
index cb4ca59..acf506d 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -9,4 +9,6 @@
# For incremental changes on EthernetManagerTest to increase coverage for existing behavior and for
# testing bug fixes.
per-file net/src/android/net/cts/EthernetManagerTest.kt = prohr@google.com #{LAST_RESORT_SUGGESTION}
+# Temporary ownership to develop APF CTS tests.
+per-file net/src/android/net/cts/ApfIntegrationTest.kt = prohr@google.com #{LAST_RESORT_SUGGESTION}
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 75%
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..00f67f4 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,9 +14,9 @@
* 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;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static org.junit.Assume.assumeTrue;
@@ -46,14 +46,15 @@
public final void tearDown() throws Exception {
super.tearDown();
+ stopApp();
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
}
@Test
public void testFgsNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
@@ -61,8 +62,8 @@
@Test
public void testActivityNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
@@ -70,23 +71,23 @@
@Test
public void testBackgroundNetworkAccess_inFullAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
assertNetworkAccess(true, null);
}
@Test
public void testBackgroundNetworkAccess_inExceptIdleAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
assertNetworkAccess(true, null);
}
}
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 88%
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..0f5f58c 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,10 +93,15 @@
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);
+ protected final long mProcessStateTransitionLongDelayMs =
+ useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(20)
+ : TimeUnit.SECONDS.toMillis(5);
+ protected final long mProcessStateTransitionShortDelayMs =
+ useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(2)
+ : TimeUnit.SECONDS.toMillis(5);
private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
@@ -110,17 +117,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 +143,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 +173,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 +194,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;
@@ -232,6 +246,22 @@
return Boolean.parseBoolean(output);
}
+ /**
+ * Check if the flag to use different delays for sensitive proc-states is enabled.
+ * This is a manual check because the feature flag infrastructure may not be available
+ * in all the branches that will get this code.
+ * TODO: b/322115994 - Use @RequiresFlagsEnabled with
+ * Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN once the tests are moved to cts.
+ */
+ private boolean useDifferentDelaysForBackgroundChain() {
+ if (!SdkLevel.isAtLeastV()) {
+ return false;
+ }
+ final String output = executeShellCommand("device_config get backstage_power"
+ + " com.android.server.net.use_different_delays_for_background_chain");
+ return Boolean.parseBoolean(output);
+ }
+
protected int getUid(String packageName) throws Exception {
return mContext.getPackageManager().getPackageUid(packageName, 0);
}
@@ -502,25 +532,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;
@@ -817,6 +845,10 @@
assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
}
+ protected void stopApp() {
+ executeSilentShellCommand("am stop-app " + TEST_APP2_PKG);
+ }
+
protected void setAppIdle(boolean isIdle) throws Exception {
setAppIdleNoAssert(isIdle);
assertAppIdle(isIdle);
@@ -926,33 +958,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 +995,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 +1010,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 +1023,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 +1067,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 79%
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..bfccce9 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();
+ stopApp();
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);
@@ -103,7 +108,8 @@
assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
assertLaunchedActivityHasNetworkAccess("testStartActivity_default", () -> {
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
+ 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..3934cfa 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;
@@ -268,6 +268,7 @@
setRestrictBackground(false);
setBatterySaverMode(false);
unregisterNetworkCallback();
+ stopApp();
if (SdkLevel.isAtLeastT() && (mCtsNetUtils != null)) {
mCtsNetUtils.restorePrivateDnsSetting();
@@ -387,7 +388,7 @@
finishActivity();
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkAccess(false, null);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
@@ -413,7 +414,7 @@
finishActivity();
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkAccess(false, null);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
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 92%
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..6c5f2ff 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,19 @@
* 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_LAST_ACTIVITY;
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;
@@ -65,6 +66,7 @@
setRestrictBackground(false);
setRestrictedNetworkingMode(false);
unregisterNetworkCallback();
+ stopApp();
}
@Test
@@ -248,8 +250,8 @@
assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
try {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
@@ -260,7 +262,7 @@
finishActivity();
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
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 454940f..e186c6b 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,7 +22,9 @@
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_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.AF_INET;
@@ -50,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;
@@ -72,6 +75,7 @@
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.InetAddresses;
import android.net.IpSecManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -91,6 +95,7 @@
import android.net.cts.util.CtsNetUtils;
import android.net.util.KeepaliveUtils;
import android.net.wifi.WifiManager;
+import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -120,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;
@@ -217,6 +224,7 @@
private WifiManager mWifiManager;
private RemoteSocketFactoryClient mRemoteSocketFactoryClient;
private CtsNetUtils mCtsNetUtils;
+ private ConnectUtil mConnectUtil;
private PackageManager mPackageManager;
private Context mTestContext;
private Context mTargetContext;
@@ -230,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");
@@ -262,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();
@@ -879,17 +892,14 @@
@Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public void testChangeUnderlyingNetworks() throws Exception {
- assumeTrue(supportedHardware());
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
final TestableNetworkCallback callback = new TestableNetworkCallback();
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.
@@ -938,7 +948,6 @@
@Test
public void testDefault() throws Exception {
- assumeTrue(supportedHardware());
if (!SdkLevel.isAtLeastS() && (
SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
|| SystemProperties.getInt("service.adb.tcp.port", -1) > -1)) {
@@ -962,19 +971,30 @@
final TestableNetworkCallback otherUidCallback = new TestableNetworkCallback();
final TestableNetworkCallback myUidCallback = new TestableNetworkCallback();
if (SdkLevel.isAtLeastS()) {
- final int otherUid =
- UserHandle.of(5 /* userId */).getUid(Process.FIRST_APPLICATION_UID);
+ // Using the same appId with the test to make sure otherUid has the internet permission.
+ // This works because the UID permission map only stores the app ID and not the whole
+ // UID. If the otherUid does not have the internet permission, network access from
+ // otherUid could be considered blocked on V+.
+ final int appId = UserHandle.getAppId(Process.myUid());
+ final int otherUid = UserHandle.of(5 /* userId */).getUid(appId);
final Handler h = new Handler(Looper.getMainLooper());
runWithShellPermissionIdentity(() -> {
registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
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);
@@ -1017,8 +1037,8 @@
// This needs to be done before testing private DNS because checkStrictModePrivateDns
// will set the private DNS server to a nonexistent name, which will cause validation to
// fail and could cause the default network to switch (e.g., from wifi to cellular).
- systemDefaultCallback.assertNoCallback();
- otherUidCallback.assertNoCallback();
+ assertNoCallbackExceptCapOrLpChange(systemDefaultCallback);
+ assertNoCallbackExceptCapOrLpChange(otherUidCallback);
}
checkStrictModePrivateDns();
@@ -1026,10 +1046,13 @@
receiver.unregisterQuietly();
}
+ private void assertNoCallbackExceptCapOrLpChange(TestableNetworkCallback callback) {
+ callback.assertNoCallback(c -> !(c instanceof CallbackEntry.CapabilitiesChanged
+ || c instanceof CallbackEntry.LinkPropertiesChanged));
+ }
+
@Test
public void testAppAllowed() throws Exception {
- assumeTrue(supportedHardware());
-
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
// Shell app must not be put in here or it would kill the ADB-over-network use case
@@ -1137,8 +1160,6 @@
}
private void doTestAutomaticOnOffKeepaliveMode(final boolean closeSocket) throws Exception {
- assumeTrue(supportedHardware());
-
// Get default network first before starting VPN
final Network defaultNetwork = mCM.getActiveNetwork();
final TestableNetworkCallback cb = new TestableNetworkCallback();
@@ -1226,8 +1247,6 @@
@Test
public void testAppDisallowed() throws Exception {
- assumeTrue(supportedHardware());
-
FileDescriptor localFd = openSocketFd(TEST_HOST, 80, TIMEOUT_MS);
FileDescriptor remoteFd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
@@ -1260,8 +1279,6 @@
@Test
public void testSocketClosed() throws Exception {
- assumeTrue(supportedHardware());
-
final FileDescriptor localFd = openSocketFd(TEST_HOST, 80, TIMEOUT_MS);
final List<FileDescriptor> remoteFds = new ArrayList<>();
@@ -1285,7 +1302,6 @@
@Test
public void testExcludedRoutes() throws Exception {
- assumeTrue(supportedHardware());
assumeTrue(SdkLevel.isAtLeastT());
// Shell app must not be put in here or it would kill the ADB-over-network use case
@@ -1306,8 +1322,6 @@
@Test
public void testIncludedRoutes() throws Exception {
- assumeTrue(supportedHardware());
-
// Shell app must not be put in here or it would kill the ADB-over-network use case
String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
startVpn(new String[]{"192.0.2.2/32", "2001:db8:1:2::ffe/128"} /* addresses */,
@@ -1325,7 +1339,6 @@
@Test
public void testInterleavedRoutes() throws Exception {
- assumeTrue(supportedHardware());
assumeTrue(SdkLevel.isAtLeastT());
// Shell app must not be put in here or it would kill the ADB-over-network use case
@@ -1353,8 +1366,6 @@
@Test
public void testGetConnectionOwnerUidSecurity() throws Exception {
- assumeTrue(supportedHardware());
-
DatagramSocket s;
InetAddress address = InetAddress.getByName("localhost");
s = new DatagramSocket();
@@ -1375,7 +1386,6 @@
@Test
public void testSetProxy() throws Exception {
- assumeTrue(supportedHardware());
ProxyInfo initialProxy = mCM.getDefaultProxy();
// Receiver for the proxy change broadcast.
BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
@@ -1415,7 +1425,6 @@
@Test
public void testSetProxyDisallowedApps() throws Exception {
- assumeTrue(supportedHardware());
ProxyInfo initialProxy = mCM.getDefaultProxy();
String disallowedApps = mPackageName;
@@ -1441,7 +1450,6 @@
@Test
public void testNoProxy() throws Exception {
- assumeTrue(supportedHardware());
ProxyInfo initialProxy = mCM.getDefaultProxy();
BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
proxyBroadcastReceiver.register();
@@ -1476,7 +1484,6 @@
@Test
public void testBindToNetworkWithProxy() throws Exception {
- assumeTrue(supportedHardware());
String allowedApps = mPackageName;
Network initialNetwork = mCM.getActiveNetwork();
ProxyInfo initialProxy = mCM.getDefaultProxy();
@@ -1501,9 +1508,6 @@
@Test
public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception {
- if (!supportedHardware()) {
- return;
- }
// VPN is not routing any traffic i.e. its underlying networks is an empty array.
ArrayList<Network> underlyingNetworks = new ArrayList<>();
String allowedApps = mPackageName;
@@ -1533,9 +1537,6 @@
@Test
public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
- if (!supportedHardware()) {
- return;
- }
Network underlyingNetwork = mCM.getActiveNetwork();
if (underlyingNetwork == null) {
Log.i(TAG, "testVpnMeterednessWithNullUnderlyingNetwork cannot execute"
@@ -1562,9 +1563,6 @@
@Test
public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
- if (!supportedHardware()) {
- return;
- }
Network underlyingNetwork = mCM.getActiveNetwork();
if (underlyingNetwork == null) {
Log.i(TAG, "testVpnMeterednessWithNonNullUnderlyingNetwork cannot execute"
@@ -1604,9 +1602,6 @@
@Test
public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
- if (!supportedHardware()) {
- return;
- }
Network underlyingNetwork = mCM.getActiveNetwork();
if (underlyingNetwork == null) {
Log.i(TAG, "testAlwaysMeteredVpnWithNullUnderlyingNetwork cannot execute"
@@ -1631,9 +1626,6 @@
@Test
public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
- if (!supportedHardware()) {
- return;
- }
Network underlyingNetwork = mCM.getActiveNetwork();
if (underlyingNetwork == null) {
Log.i(TAG, "testAlwaysMeteredVpnWithNonNullUnderlyingNetwork cannot execute"
@@ -1671,9 +1663,6 @@
@Test
public void testB141603906() throws Exception {
- if (!supportedHardware()) {
- return;
- }
final InetSocketAddress src = new InetSocketAddress(0);
final InetSocketAddress dst = new InetSocketAddress(0);
final int NUM_THREADS = 8;
@@ -1781,8 +1770,6 @@
*/
@Test
public void testDownloadWithDownloadManagerDisallowed() throws Exception {
- assumeTrue(supportedHardware());
-
// Start a VPN with DownloadManager package in disallowed list.
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
new String[] {"192.0.2.0/24", "2001:db8::/32"},
@@ -1838,7 +1825,6 @@
@Test @IgnoreUpTo(Build.VERSION_CODES.R)
public void testBlockIncomingPackets() throws Exception {
- assumeTrue(supportedHardware());
final Network network = mCM.getActiveNetwork();
assertNotNull("Requires a working Internet connection", network);
@@ -1907,7 +1893,6 @@
@Test
public void testSetVpnDefaultForUids() throws Exception {
- assumeTrue(supportedHardware());
assumeTrue(SdkLevel.isAtLeastU());
final Network defaultNetwork = mCM.getActiveNetwork();
@@ -1953,6 +1938,81 @@
});
}
+ /**
+ * Check if packets to a VPN interface's IP arriving on a non-VPN interface are dropped or not.
+ * If the test interface has a different address from the VPN interface, packets must be dropped
+ * If the test interface has the same address as the VPN interface, packets must not be
+ * dropped
+ *
+ * @param duplicatedAddress true to bring up the test interface with the same address as the VPN
+ * interface
+ */
+ private void doTestDropPacketToVpnAddress(final boolean duplicatedAddress)
+ throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .addTransportType(TRANSPORT_TEST)
+ .build();
+ final CtsNetUtils.TestNetworkCallback callback = new CtsNetUtils.TestNetworkCallback();
+ mCM.requestNetwork(request, callback);
+ final ParcelFileDescriptor srcTunFd = runWithShellPermissionIdentity(() -> {
+ final TestNetworkManager tnm = mTestContext.getSystemService(TestNetworkManager.class);
+ List<LinkAddress> linkAddresses = duplicatedAddress
+ ? List.of(new LinkAddress("192.0.2.2/24"),
+ new LinkAddress("2001:db8:1:2::ffe/64")) :
+ List.of(new LinkAddress("198.51.100.2/24"),
+ new LinkAddress("2001:db8:3:4::ffe/64"));
+ final TestNetworkInterface iface = tnm.createTunInterface(linkAddresses);
+ tnm.setupTestNetwork(iface.getInterfaceName(), new Binder());
+ return iface.getFileDescriptor();
+ }, MANAGE_TEST_NETWORKS);
+ final Network testNetwork = callback.waitForAvailable();
+ assertNotNull(testNetwork);
+ final DatagramSocket dstSock = new DatagramSocket();
+
+ testAndCleanup(() -> {
+ startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"} /* addresses */,
+ new String[]{"0.0.0.0/0", "::/0"} /* routes */,
+ "" /* allowedApplications */, "" /* disallowedApplications */,
+ null /* proxyInfo */, null /* underlyingNetworks */,
+ false /* isAlwaysMetered */);
+
+ final FileDescriptor dstUdpFd = dstSock.getFileDescriptor$();
+ 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.getFileDescriptor(), dstUdpFd,
+ InetAddresses.parseNumericAddress("2001:db8:1:2::ffe") /* dstAddress */,
+ InetAddresses.parseNumericAddress("2001:db8:1:2::ffa") /* srcAddress */,
+ duplicatedAddress ? EXPECT_PASS : EXPECT_BLOCK);
+
+ // Traffic on VPN should not be affected
+ checkTrafficOnVpn();
+ }, /* cleanup */ () -> {
+ srcTunFd.close();
+ dstSock.close();
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mTestContext.getSystemService(TestNetworkManager.class)
+ .teardownTestNetwork(testNetwork);
+ }, MANAGE_TEST_NETWORKS);
+ }, /* cleanup */ () -> {
+ mCM.unregisterNetworkCallback(callback);
+ });
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDropPacketToVpnAddress_WithoutDuplicatedAddress() throws Exception {
+ doTestDropPacketToVpnAddress(false /* duplicatedAddress */);
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDropPacketToVpnAddress_WithDuplicatedAddress() throws Exception {
+ doTestDropPacketToVpnAddress(true /* duplicatedAddress */);
+ }
+
private ByteBuffer buildIpv4UdpPacket(final Inet4Address dstAddr, final Inet4Address srcAddr,
final short dstPort, final short srcPort, final byte[] payload) throws IOException {
@@ -1996,7 +2056,8 @@
private void checkBlockUdp(
final FileDescriptor srcTunFd,
final FileDescriptor dstUdpFd,
- final boolean ipv6,
+ final InetAddress dstAddress,
+ final InetAddress srcAddress,
final boolean expectBlock) throws Exception {
final Random random = new Random();
final byte[] sendData = new byte[100];
@@ -2004,15 +2065,15 @@
final short dstPort = (short) ((InetSocketAddress) Os.getsockname(dstUdpFd)).getPort();
ByteBuffer buf;
- if (ipv6) {
+ if (dstAddress instanceof Inet6Address) {
buf = buildIpv6UdpPacket(
- (Inet6Address) TEST_IP6_DST_ADDR.getAddress(),
- (Inet6Address) TEST_IP6_SRC_ADDR.getAddress(),
+ (Inet6Address) dstAddress,
+ (Inet6Address) srcAddress,
dstPort, TEST_SRC_PORT, sendData);
} else {
buf = buildIpv4UdpPacket(
- (Inet4Address) TEST_IP4_DST_ADDR.getAddress(),
- (Inet4Address) TEST_IP4_SRC_ADDR.getAddress(),
+ (Inet4Address) dstAddress,
+ (Inet4Address) srcAddress,
dstPort, TEST_SRC_PORT, sendData);
}
@@ -2038,8 +2099,10 @@
final FileDescriptor srcTunFd,
final FileDescriptor dstUdpFd,
final boolean expectBlock) throws Exception {
- checkBlockUdp(srcTunFd, dstUdpFd, false /* ipv6 */, expectBlock);
- checkBlockUdp(srcTunFd, dstUdpFd, true /* ipv6 */, expectBlock);
+ checkBlockUdp(srcTunFd, dstUdpFd, TEST_IP4_DST_ADDR.getAddress(),
+ TEST_IP4_SRC_ADDR.getAddress(), expectBlock);
+ checkBlockUdp(srcTunFd, dstUdpFd, TEST_IP6_DST_ADDR.getAddress(),
+ TEST_IP6_SRC_ADDR.getAddress(), expectBlock);
}
private class DetailedBlockedStatusCallback extends TestableNetworkCallback {
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index c526172..12ea23b 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -35,5 +35,5 @@
"general-tests",
"sts",
],
- certificate: ":cts-net-app",
+ sdk_version: "test_current",
}
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/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
index 100b6e4..c220000 100644
--- a/tests/cts/hostside/networkslicingtestapp/Android.bp
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -44,6 +44,7 @@
"CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithoutProperty.xml",
+ sdk_version: "test_current",
}
android_test_helper_app {
@@ -53,6 +54,7 @@
"CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithProperty.xml",
+ sdk_version: "test_current",
}
android_test_helper_app {
@@ -63,4 +65,5 @@
],
target_sdk_version: "33",
manifest: "AndroidManifestWithoutProperty.xml",
+ sdk_version: "test_current",
}
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/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 4f21af7..cea60f9 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -18,8 +18,6 @@
import android.platform.test.annotations.RequiresDevice;
-import com.android.testutils.SkipPresubmit;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -37,13 +35,11 @@
uninstallPackage(TEST_APP2_PKG, true);
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testChangeUnderlyingNetworks() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testChangeUnderlyingNetworks");
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testDefault() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
@@ -166,9 +162,20 @@
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testSetVpnDefaultForUids() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetVpnDefaultForUids");
}
+
+ @Test
+ public void testDropPacketToVpnAddress_WithoutDuplicatedAddress() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest",
+ "testDropPacketToVpnAddress_WithoutDuplicatedAddress");
+ }
+
+ @Test
+ public void testDropPacketToVpnAddress_WithDuplicatedAddress() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest",
+ "testDropPacketToVpnAddress_WithDuplicatedAddress");
+ }
}
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index 5ac4229..1d30d68 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",
+ "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..abd6fe2 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -1,23 +1,17 @@
# 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
+from utils import mdns_utils
+from utils import tether_utils
+from utils.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,67 +34,53 @@
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)
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, 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)
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, 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
+ )
+ def test_mdns_via_hotspot(self):
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ 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.NONE
+ )
+ mdns_utils.register_mdns_service_and_discover_resolve(
+ self.clientDevice, self.serverDevice
+ )
+ finally:
+ mdns_utils.cleanup_mdns_service(
+ self.clientDevice, self.serverDevice
+ )
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.NONE
+ )
if __name__ == "__main__":
# Take test args
diff --git a/tests/cts/multidevices/snippet/Android.bp b/tests/cts/multidevices/snippet/Android.bp
index 5940cbb..b0b32c2 100644
--- a/tests/cts/multidevices/snippet/Android.bp
+++ b/tests/cts/multidevices/snippet/Android.bp
@@ -25,6 +25,7 @@
],
srcs: [
"ConnectivityMultiDevicesSnippet.kt",
+ "MdnsMultiDevicesSnippet.kt",
],
manifest: "AndroidManifest.xml",
static_libs: [
diff --git a/tests/cts/multidevices/snippet/AndroidManifest.xml b/tests/cts/multidevices/snippet/AndroidManifest.xml
index 9ed8146..967e581 100644
--- a/tests/cts/multidevices/snippet/AndroidManifest.xml
+++ b/tests/cts/multidevices/snippet/AndroidManifest.xml
@@ -27,7 +27,8 @@
of a snippet class -->
<meta-data
android:name="mobly-snippets"
- android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet" />
+ android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet,
+ com.google.snippet.connectivity.MdnsMultiDevicesSnippet" />
</application>
<!-- Add an instrumentation tag so that the app can be launched through an
instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index 115210b..f4ad2c4 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()
+ @Rpc(description = "Unregister all connections.")
+ fun unregisterAll() {
+ cbHelper.unregisterAll()
}
@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/snippet/MdnsMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/MdnsMultiDevicesSnippet.kt
new file mode 100644
index 0000000..1b288df
--- /dev/null
+++ b/tests/cts/multidevices/snippet/MdnsMultiDevicesSnippet.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.google.snippet.connectivity
+
+import android.net.nsd.NsdManager
+import android.net.nsd.NsdServiceInfo
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.NsdDiscoveryRecord
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
+import com.android.testutils.NsdRegistrationRecord
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
+import com.android.testutils.NsdResolveRecord
+import com.android.testutils.NsdResolveRecord.ResolveEvent.ServiceResolved
+import com.google.android.mobly.snippet.Snippet
+import com.google.android.mobly.snippet.rpc.Rpc
+import kotlin.test.assertEquals
+import org.junit.Assert.assertArrayEquals
+
+private const val SERVICE_NAME = "MultiDevicesTest"
+private const val SERVICE_TYPE = "_multi_devices._tcp"
+private const val SERVICE_ATTRIBUTES_KEY = "key"
+private const val SERVICE_ATTRIBUTES_VALUE = "value"
+private const val SERVICE_PORT = 12345
+private const val REGISTRATION_TIMEOUT_MS = 10_000L
+
+class MdnsMultiDevicesSnippet : Snippet {
+ private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ private val nsdManager = context.getSystemService(NsdManager::class.java)!!
+ private val registrationRecord = NsdRegistrationRecord()
+ private val discoveryRecord = NsdDiscoveryRecord()
+ private val resolveRecord = NsdResolveRecord()
+
+ @Rpc(description = "Register a mDns service")
+ fun registerMDnsService() {
+ val info = NsdServiceInfo()
+ info.setServiceName(SERVICE_NAME)
+ info.setServiceType(SERVICE_TYPE)
+ info.setPort(SERVICE_PORT)
+ info.setAttribute(SERVICE_ATTRIBUTES_KEY, SERVICE_ATTRIBUTES_VALUE)
+ nsdManager.registerService(info, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
+ registrationRecord.expectCallback<ServiceRegistered>(REGISTRATION_TIMEOUT_MS)
+ }
+
+ @Rpc(description = "Unregister a mDns service")
+ fun unregisterMDnsService() {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+
+ @Rpc(description = "Ensure the discovery and resolution of the mDNS service")
+ // Suppress the warning, as the NsdManager#resolveService() method is deprecated.
+ @Suppress("DEPRECATION")
+ fun ensureMDnsServiceDiscoveryAndResolution() {
+ // Discover a mDns service that matches the test service
+ nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+ val info = discoveryRecord.waitForServiceDiscovered(SERVICE_NAME, SERVICE_TYPE)
+ // Resolve the retrieved mDns service.
+ nsdManager.resolveService(info, resolveRecord)
+ val serviceResolved = resolveRecord.expectCallbackEventually<ServiceResolved>()
+ serviceResolved.serviceInfo.let {
+ assertEquals(SERVICE_NAME, it.serviceName)
+ assertEquals(".$SERVICE_TYPE", it.serviceType)
+ assertEquals(SERVICE_PORT, it.port)
+ assertEquals(1, it.attributes.size)
+ assertArrayEquals(
+ SERVICE_ATTRIBUTES_VALUE.encodeToByteArray(),
+ it.attributes[SERVICE_ATTRIBUTES_KEY]
+ )
+ }
+ }
+
+ @Rpc(description = "Stop discovery")
+ fun stopMDnsServiceDiscovery() {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallbackEventually<DiscoveryStopped>()
+ }
+}
diff --git a/tests/cts/multidevices/utils/mdns_utils.py b/tests/cts/multidevices/utils/mdns_utils.py
new file mode 100644
index 0000000..ec1fea0
--- /dev/null
+++ b/tests/cts/multidevices/utils/mdns_utils.py
@@ -0,0 +1,42 @@
+# 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.
+
+from mobly.controllers import android_device
+
+
+def register_mdns_service_and_discover_resolve(
+ advertising_device: android_device, discovery_device: android_device
+) -> None:
+ """Test mdns advertising, discovery and resolution
+
+ One device registers an mDNS service, and another device discovers and
+ resolves that service.
+ """
+ advertising = advertising_device.connectivity_multi_devices_snippet
+ discovery = discovery_device.connectivity_multi_devices_snippet
+
+ # Register a mDns service
+ advertising.registerMDnsService()
+
+ # Ensure the discovery and resolution of the mDNS service
+ discovery.ensureMDnsServiceDiscoveryAndResolution()
+
+
+def cleanup_mdns_service(
+ advertising_device: android_device, discovery_device: android_device
+) -> None:
+ # Unregister the mDns service
+ advertising_device.connectivity_multi_devices_snippet.unregisterMDnsService()
+ # Stop discovery
+ discovery_device.connectivity_multi_devices_snippet.stopMDnsServiceDiscovery()
diff --git a/tests/cts/multidevices/utils/tether_utils.py b/tests/cts/multidevices/utils/tether_utils.py
new file mode 100644
index 0000000..702b596
--- /dev/null
+++ b/tests/cts/multidevices/utils/tether_utils.py
@@ -0,0 +1,110 @@
+# 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.
+
+import base64
+import uuid
+
+from mobly import asserts
+from mobly.controllers import android_device
+
+
+class UpstreamType:
+ NONE = 0
+ 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 assume_hotspot_test_preconditions(
+ server_device: android_device,
+ client_device: android_device,
+ upstream_type: UpstreamType,
+) -> None:
+ 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"
+ )
+ elif upstream_type == UpstreamType.WIFI:
+ asserts.skip_if(
+ not server.isStaApConcurrencySupported(),
+ "Server requires Wifi AP + STA concurrency",
+ )
+ elif upstream_type == UpstreamType.NONE:
+ pass
+ else:
+ raise ValueError(f"Invalid upstream type: {upstream_type}")
+
+
+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
+
+ if upstream_type == UpstreamType.CELLULAR:
+ server.requestCellularAndEnsureDefault()
+ elif upstream_type == UpstreamType.WIFI:
+ server.ensureWifiIsDefault()
+ elif upstream_type == UpstreamType.NONE:
+ pass
+ 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.unregisterAll()
+ # Teardown the hotspot.
+ server.stopAllTethering()
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 074c587..ae85701 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",
@@ -179,4 +180,5 @@
"cts",
"general-tests",
],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 38f26d8..077c3ef 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -24,6 +24,11 @@
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk" />
<option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.tethering.apex" />
<option name="not-shardable" value="true" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+ <option name="target" value="device" />
+ <option name="config-filename" value="{MODULE}" />
+ <option name="version" value="1.0" />
+ </target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="{MODULE}.apk" />
@@ -38,6 +43,7 @@
<option name="runtime-hint" value="9m4s" />
<option name="hidden-api-checks" value="false" />
<option name="isolated-storage" value="false" />
+ <option name="instrumentation-arg" key="test-module-name" value="{MODULE}" />
<!-- Test filter that allows test APKs to select which tests they want to run by annotating
those tests with an annotation matching the name of the APK.
diff --git a/Cronet/tests/cts/assets/html/hello_world.html b/tests/cts/net/DynamicConfig.xml
similarity index 65%
rename from Cronet/tests/cts/assets/html/hello_world.html
rename to tests/cts/net/DynamicConfig.xml
index ea62ce2..af019c2 100644
--- a/Cronet/tests/cts/assets/html/hello_world.html
+++ b/tests/cts/net/DynamicConfig.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ 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.
@@ -14,11 +14,11 @@
~ 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
+<dynamicConfig>
+ <entry key="remote_config_required">
+ <value>false</value>
+ </entry>
+ <entry key="IP_ADDRESS_ECHO_URL">
+ <value>https://google-ipv6test.appspot.com/ip.js?fmt=text</value>
+ </entry>
+</dynamicConfig>
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 2ec3a70..587d5a5 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -56,4 +56,5 @@
":CtsNetTestAppForApi23",
],
per_testcase_directory: true,
+ sdk_version: "test_current",
}
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index 8e24fba..de4a3bf 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -3,6 +3,11 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+filegroup {
+ name: "dns_async_test_default_map",
+ srcs: ["dns_async_test_default.map"],
+}
+
cc_defaults {
name: "dns_async_defaults",
@@ -20,6 +25,14 @@
srcs: [
"NativeDnsAsyncTest.cpp",
],
+ // This test runs on older platform versions, so many libraries (such as libbase and libc++)
+ // need to be linked statically. The test also needs 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 platform itself uses.
+ // See http://b/333438055 for an example of what goes wrong when libc++ is partially exported
+ // from an executable.
+ version_script: ":dns_async_test_default_map",
+ stl: "libc++_static",
shared_libs: [
"libandroid",
"liblog",
diff --git a/tests/cts/net/native/dns/dns_async_test_default.map b/tests/cts/net/native/dns/dns_async_test_default.map
new file mode 100644
index 0000000..e342e43
--- /dev/null
+++ b/tests/cts/net/native/dns/dns_async_test_default.map
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+{
+ local:
+ *;
+};
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
new file mode 100644
index 0000000..f6cbeeb
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -0,0 +1,625 @@
+/*
+ * 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.
+ */
+// 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.ApfCounterTracker
+import android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS
+import android.net.apf.ApfV4Generator
+import android.net.apf.ApfV4GeneratorBase
+import android.net.apf.ApfV6Generator
+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.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.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.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
+// 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
+ // LegacyApfFilter.java from being used.
+ runAsShell(WRITE_DEVICE_CONFIG) {
+ DeviceConfig.setProperty(
+ NAMESPACE_CONNECTIVITY,
+ APF_NEW_RA_FILTER_VERSION,
+ "1", // value => force enabled
+ false // makeDefault
+ )
+ }
+ }
+
+ @AfterClass
+ @JvmStatic
+ fun tearDownOnce() {
+ turnScreenOn()
+ }
+ }
+
+ 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() {
+ assume().that(pm.hasSystemFeature(FEATURE_WIFI)).isTrue()
+
+ networkCallback = TestableNetworkCallback()
+ cm.requestNetwork(
+ NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build(),
+ networkCallback
+ )
+ network = networkCallback.expect<Available>().network
+ networkCallback.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) {
+ ifname = assertNotNull(it.lp.interfaceName)
+ true
+ }
+ // 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)
+ }
+ }
+
+ @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 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)
+ // Increase timeout for test to 15 minutes to accommodate device with large APF RAM.
+ @Test(timeout = 15 * 60 * 1000)
+ 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 ApfV4GeneratorBase<*>.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(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ ).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(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+
+ // 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(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+
+ // 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(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+
+ // 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.
+ val timeDiff = filterAgeSeconds - filterAgeSecondsOrig
+ assertThat(timeDiff).isAnyOf(5, 6)
+ }
+
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
+ @Test
+ fun testFilterAge16384thsIncreasesBetweenPackets() {
+ assumeApfVersionSupportAtLeast(6000)
+ clearApfMemory()
+ val gen = ApfV6Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+
+ // If not ICMPv6 Echo Reply -> PASS
+ gen.addPassIfNotIcmpv6EchoReply()
+
+ // Store all prefilled memory slots in counter region [500, 520)
+ gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS)
+ gen.addStoreCounter(FILTER_AGE_16384THS, R0)
+
+ 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 apfRam = readProgram()
+ val filterAge16384thSecondsOrig =
+ ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
+
+ Thread.sleep(5000)
+
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ apfRam = readProgram()
+ val filterAge16384thSeconds = ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
+ val timeDiff = (filterAge16384thSeconds - filterAge16384thSecondsOrig)
+ // Expect the HAL plus ping latency to be less than 800ms.
+ val timeDiffLowerBound = (4.99 * 16384).toInt()
+ val timeDiffUpperBound = (5.81 * 16384).toInt()
+ // Assert that filter age has increased, but not too much.
+ assertThat(timeDiff).isGreaterThan(timeDiffLowerBound)
+ assertThat(timeDiff).isLessThan(timeDiffUpperBound)
+ }
+}
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..21eb90f 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;
@@ -162,6 +174,7 @@
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
@@ -180,10 +193,11 @@
import android.util.Log;
import android.util.Range;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
@@ -191,15 +205,16 @@
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;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
-import com.android.testutils.SkipMainlinePresubmit;
import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
@@ -211,6 +226,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;
@@ -234,6 +250,7 @@
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -259,10 +276,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 +311,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;
@@ -315,6 +337,11 @@
private static final String TEST_HTTPS_URL_PATH = "/https_path";
private static final String TEST_HTTP_URL_PATH = "/http_path";
private static final String LOCALHOST_HOSTNAME = "localhost";
+ private static final String TEST_MODULE_NAME_OPTION = "test-module-name";
+ private static final String IP_ADDRESS_ECHO_URL_KEY = "IP_ADDRESS_ECHO_URL";
+ private static final List<String> ALLOWED_IP_ADDRESS_ECHO_URLS = Arrays.asList(
+ "https://google-ipv6test.appspot.com/ip.js?fmt=text",
+ "https://ipv6test.googleapis-cn.com/ip.js?fmt=text");
// Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
private static final long WIFI_CONNECT_TIMEOUT_MS = 60_000L;
@@ -328,8 +355,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 +436,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 +445,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 +572,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));
@@ -845,7 +862,7 @@
* Tests that connections can be opened on WiFi and cellphone networks,
* and that they are made from different IP addresses.
*/
- @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @AppModeFull(reason = "Cannot get WifiManager or access the SD card in instant app mode")
@Test
@RequiresDevice // Virtual devices use a single internet connection for all networks
public void testOpenConnection() throws Exception {
@@ -853,9 +870,10 @@
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 ipAddressEchoUrl = getIpAddressEchoUrlFromConfig();
+ URL url = new URL(ipAddressEchoUrl);
String wifiAddressString = httpGet(wifiNetwork, url);
String cellAddressString = httpGet(cellNetwork, url);
@@ -872,6 +890,19 @@
}
/**
+ * Gets IP address echo url from dynamic config.
+ */
+ private static String getIpAddressEchoUrlFromConfig() throws Exception {
+ Bundle instrumentationArgs = InstrumentationRegistry.getArguments();
+ String testModuleName = instrumentationArgs.getString(TEST_MODULE_NAME_OPTION);
+ // Get the DynamicConfig.xml contents and extract the ipv6 test URL.
+ DynamicConfigDeviceSide dynamicConfig = new DynamicConfigDeviceSide(testModuleName);
+ String ipAddressEchoUrl = dynamicConfig.getValue(IP_ADDRESS_ECHO_URL_KEY);
+ assertContains(ALLOWED_IP_ADDRESS_ECHO_URLS, ipAddressEchoUrl);
+ return ipAddressEchoUrl;
+ }
+
+ /**
* Performs a HTTP GET to the specified URL on the specified Network, and returns
* the response body decoded as UTF-8.
*/
@@ -993,10 +1024,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();
}
@@ -1022,15 +1053,14 @@
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
- @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testIsPrivateDnsBroken() throws InterruptedException {
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 +1098,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 +1127,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 +1154,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);
}
@@ -1226,13 +1257,14 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(broadcastAction);
+ final CompletableFuture<NetworkRequest> requestFuture = new CompletableFuture<>();
final CompletableFuture<Network> networkFuture = new CompletableFuture<>();
final AtomicInteger receivedCount = new AtomicInteger(0);
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final NetworkRequest request = intent.getParcelableExtra(EXTRA_NETWORK_REQUEST);
- assertPendingIntentRequestMatches(request, secondRequest, useListen);
+ requestFuture.complete(request);
receivedCount.incrementAndGet();
networkFuture.complete(intent.getParcelableExtra(EXTRA_NETWORK));
}
@@ -1247,6 +1279,9 @@
} catch (TimeoutException e) {
throw new AssertionError("PendingIntent not received for " + secondRequest, e);
}
+ assertPendingIntentRequestMatches(
+ requestFuture.get(NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS),
+ secondRequest, useListen);
// Sleep for a small amount of time to try to check that only one callback is ever
// received (so the first callback was really unregistered). This does not guarantee
@@ -1294,15 +1329,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 +1354,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 +1447,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 +2064,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 +2130,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 +2154,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 +2239,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 +2267,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 +2280,7 @@
private void registerCallbackAndWaitForAvailable(@NonNull final NetworkRequest request,
@NonNull final TestableNetworkCallback cb) {
- registerNetworkCallback(request, cb);
+ networkCallbackRule.registerNetworkCallback(request, cb);
waitForAvailable(cb);
}
@@ -2347,21 +2388,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 +2423,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 +2433,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 +2512,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 +2537,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 +2596,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 +2870,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 +2988,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 +3027,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 +3064,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 +3139,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 +3243,6 @@
if (supportWifi) {
mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
- } else {
- mCtsNetUtils.disconnectFromCell();
}
final CompletableFuture<Boolean> future = new CompletableFuture<>();
@@ -3226,7 +3253,7 @@
if (supportWifi) {
mCtsNetUtils.ensureWifiConnected();
} else {
- mCtsNetUtils.connectToCell();
+ networkCallbackRule.requestCell();
}
assertTrue(future.get(LISTEN_ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}, () -> {
@@ -3267,13 +3294,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 +3370,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 +3404,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 +3437,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 +3455,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 +3600,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 +3756,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 b7e5205..61ebd8f 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -303,18 +303,6 @@
fun expectOnAvailable(timeout: Long = TIMEOUT_MS): String {
return available.get(timeout, TimeUnit.MILLISECONDS)
}
-
- fun expectOnUnavailable() {
- // Assert that the future fails with the IllegalStateException from the
- // completeExceptionally() call inside onUnavailable.
- assertFailsWith(IllegalStateException::class) {
- try {
- available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
- } catch (e: ExecutionException) {
- throw e.cause!!
- }
- }
- }
}
private class EthernetOutcomeReceiver :
@@ -348,7 +336,9 @@
}
}
- private fun isEthernetSupported() = em != null
+ private fun isEthernetSupported() : Boolean {
+ return context.getSystemService(EthernetManager::class.java) != null
+ }
@Before
fun setUp() {
@@ -899,6 +889,24 @@
}
@Test
+ fun testEnableDisableInterface_callbacks() {
+ 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)
+ }
+
+ @Test
fun testUpdateConfiguration_forBothIpConfigAndCapabilities() {
val iface = createInterface()
val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
index 7f710d7..2a6c638 100644
--- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
@@ -26,12 +26,15 @@
import static android.system.OsConstants.FIONREAD;
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
import android.net.IpSecTransform;
+import android.net.IpSecTransformState;
+import android.os.OutcomeReceiver;
import android.platform.test.annotations.AppModeFull;
import android.system.ErrnoException;
import android.system.Os;
@@ -65,8 +68,12 @@
import java.net.SocketImpl;
import java.net.SocketOptions;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
@@ -83,6 +90,7 @@
protected static final byte[] TEST_DATA = "Best test data ever!".getBytes();
protected static final int DATA_BUFFER_LEN = 4096;
protected static final int SOCK_TIMEOUT = 500;
+ protected static final int REPLAY_BITMAP_LEN_BYTE = 512;
private static final byte[] KEY_DATA = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
@@ -122,6 +130,47 @@
.getSystemService(Context.CONNECTIVITY_SERVICE);
}
+ protected static void checkTransformState(
+ IpSecTransform transform,
+ long txHighestSeqNum,
+ long rxHighestSeqNum,
+ long packetCnt,
+ long byteCnt,
+ byte[] replayBitmap)
+ throws Exception {
+ final CompletableFuture<IpSecTransformState> futureIpSecTransform =
+ new CompletableFuture<>();
+ transform.requestIpSecTransformState(
+ Executors.newSingleThreadExecutor(),
+ new OutcomeReceiver<IpSecTransformState, RuntimeException>() {
+ @Override
+ public void onResult(IpSecTransformState state) {
+ futureIpSecTransform.complete(state);
+ }
+ });
+
+ final IpSecTransformState transformState =
+ futureIpSecTransform.get(SOCK_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ assertEquals(txHighestSeqNum, transformState.getTxHighestSequenceNumber());
+ assertEquals(rxHighestSeqNum, transformState.getRxHighestSequenceNumber());
+ assertEquals(packetCnt, transformState.getPacketCount());
+ assertEquals(byteCnt, transformState.getByteCount());
+ assertArrayEquals(replayBitmap, transformState.getReplayBitmap());
+ }
+
+ protected static void checkTransformStateNoTraffic(IpSecTransform transform) throws Exception {
+ checkTransformState(transform, 0L, 0L, 0L, 0L, newReplayBitmap(0));
+ }
+
+ protected static byte[] newReplayBitmap(int receivedPktCnt) {
+ final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BYTE * 8);
+ for (int i = 0; i < receivedPktCnt; i++) {
+ bitSet.set(i);
+ }
+ return Arrays.copyOf(bitSet.toByteArray(), REPLAY_BITMAP_LEN_BYTE);
+ }
+
/** Checks if an IPsec algorithm is enabled on the device */
protected static boolean hasIpSecAlgorithm(String algorithm) {
if (SdkLevel.isAtLeastS()) {
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index fe86a90..b5f43d3 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -63,13 +63,17 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import android.net.InetAddresses;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
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;
@@ -79,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;
@@ -98,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 {
@@ -381,6 +390,22 @@
assumeTrue("Not supported by kernel", isIpv6UdpEncapSupportedByKernel());
}
+ // TODO: b/319532485 Figure out whether to support x86_32
+ private static boolean isRequestTransformStateSupportedByKernel() {
+ return NetworkUtils.isKernel64Bit() || !NetworkUtils.isKernelX86();
+ }
+
+ // Package private for use in IpSecManagerTunnelTest
+ static boolean isRequestTransformStateSupported() {
+ return SdkLevel.isAtLeastV() && isRequestTransformStateSupportedByKernel();
+ }
+
+ // Package private for use in IpSecManagerTunnelTest
+ static void assumeRequestIpSecTransformStateSupported() {
+ assumeTrue("Not supported before V", SdkLevel.isAtLeastV());
+ assumeTrue("Not supported by kernel", isRequestTransformStateSupportedByKernel());
+ }
+
@Test
public void testCreateTransformIpv4() throws Exception {
doTestCreateTransform(IPV4_LOOPBACK, false);
@@ -426,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;
@@ -500,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());
@@ -1596,4 +1631,65 @@
assertTrue("Returned invalid port", encapSocket.getPort() != 0);
}
}
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ public void testRequestIpSecTransformState() throws Exception {
+ assumeRequestIpSecTransformStateSupported();
+
+ final InetAddress localAddr = InetAddresses.parseNumericAddress(IPV6_LOOPBACK);
+ try (SecurityParameterIndex spi = mISM.allocateSecurityParameterIndex(localAddr);
+ IpSecTransform transform =
+ buildTransportModeTransform(spi, localAddr, null /* encapSocket*/)) {
+ final SocketPair<JavaUdpSocket> sockets =
+ getJavaUdpSocketPair(localAddr, mISM, transform, false);
+
+ sockets.mLeftSock.sendTo(TEST_DATA, localAddr, sockets.mRightSock.getPort());
+ sockets.mRightSock.receive();
+
+ final int expectedPacketCount = 1;
+ final int expectedInnerPacketSize = TEST_DATA.length + UDP_HDRLEN;
+
+ checkTransformState(
+ transform,
+ expectedPacketCount,
+ expectedPacketCount,
+ 2 * (long) expectedPacketCount,
+ 2 * (long) expectedInnerPacketSize,
+ 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/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 1ede5c1..22a51d6 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -19,7 +19,9 @@
import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
import static android.net.IpSecManager.UdpEncapsulationSocket;
import static android.net.cts.IpSecManagerTest.assumeExperimentalIpv6UdpEncapSupported;
+import static android.net.cts.IpSecManagerTest.assumeRequestIpSecTransformStateSupported;
import static android.net.cts.IpSecManagerTest.isIpv6UdpEncapSupported;
+import static android.net.cts.IpSecManagerTest.isRequestTransformStateSupported;
import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
import static android.net.cts.PacketUtils.BytePayload;
@@ -117,6 +119,8 @@
private static final int TIMEOUT_MS = 500;
+ private static final int PACKET_COUNT = 5000;
+
// Static state to reduce setup/teardown
private static ConnectivityManager sCM;
private static TestNetworkManager sTNM;
@@ -256,7 +260,7 @@
}
/* Test runnables for callbacks after IPsec tunnels are set up. */
- private abstract class IpSecTunnelTestRunnable {
+ private interface IpSecTunnelTestRunnable {
/**
* Runs the test code, and returns the inner socket port, if any.
*
@@ -282,8 +286,7 @@
throws Exception;
}
- private int getPacketSize(
- int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
+ private static int getInnerPacketSize(int innerFamily, boolean transportInTunnelMode) {
int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN;
// Inner Transport mode packet size
@@ -299,6 +302,13 @@
// Inner IP Header
expectedPacketSize += innerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
+ return expectedPacketSize;
+ }
+
+ private static int getPacketSize(
+ int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
+ int expectedPacketSize = getInnerPacketSize(innerFamily, transportInTunnelMode);
+
// Tunnel mode transform size
expectedPacketSize =
PacketUtils.calculateEspPacketSize(
@@ -401,6 +411,20 @@
spi, TEST_DATA, useEncap, expectedPacketSize);
socket.close();
+ if (isRequestTransformStateSupported()) {
+ final int innerPacketSize =
+ getInnerPacketSize(innerFamily, transportInTunnelMode);
+
+ checkTransformState(
+ outTunnelTransform,
+ seqNum,
+ 0L,
+ seqNum,
+ seqNum * (long) innerPacketSize,
+ newReplayBitmap(0));
+ checkTransformStateNoTraffic(inTunnelTransform);
+ }
+
return innerSocketPort;
}
};
@@ -524,6 +548,22 @@
socket.close();
+ if (isRequestTransformStateSupported()) {
+ final int innerFamily =
+ localInner instanceof Inet4Address ? AF_INET : AF_INET6;
+ final int innerPacketSize =
+ getInnerPacketSize(innerFamily, transportInTunnelMode);
+
+ checkTransformStateNoTraffic(outTunnelTransform);
+ checkTransformState(
+ inTunnelTransform,
+ 0L,
+ seqNum,
+ seqNum,
+ seqNum * (long) innerPacketSize,
+ newReplayBitmap(seqNum));
+ }
+
return 0;
}
};
@@ -1127,6 +1167,18 @@
return innerSocketPort;
}
+ private int buildTunnelNetworkAndRunTestsSimple(int spi, IpSecTunnelTestRunnable test)
+ throws Exception {
+ return buildTunnelNetworkAndRunTests(
+ LOCAL_INNER_6,
+ REMOTE_INNER_6,
+ LOCAL_OUTER_6,
+ REMOTE_OUTER_6,
+ spi,
+ null /* encapSocket */,
+ test);
+ }
+
private static void receiveAndValidatePacket(JavaUdpSocket socket) throws Exception {
byte[] socketResponseBytes = socket.receive();
assertArrayEquals(TEST_DATA, socketResponseBytes);
@@ -1691,4 +1743,101 @@
assumeExperimentalIpv6UdpEncapSupported();
doTestMigrateTunnelModeTransform(AF_INET6, AF_INET6, true, false);
}
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ public void testRequestIpSecTransformStateForRx() throws Exception {
+ assumeRequestIpSecTransformStateSupported();
+
+ final int spi = getRandomSpi(LOCAL_OUTER_6, REMOTE_OUTER_6);
+ buildTunnelNetworkAndRunTestsSimple(
+ spi,
+ (ipsecNetwork,
+ tunnelIface,
+ tunUtils,
+ inTunnelTransform,
+ outTunnelTransform,
+ localOuter,
+ remoteOuter,
+ seqNum) -> {
+ // Build a socket and send traffic
+ final JavaUdpSocket socket = new JavaUdpSocket(LOCAL_INNER_6);
+ ipsecNetwork.bindSocket(socket.mSocket);
+ int innerSocketPort = socket.getPort();
+
+ for (int i = 1; i < PACKET_COUNT + 1; i++) {
+ byte[] pkt =
+ getTunnelModePacket(
+ spi,
+ REMOTE_INNER_6,
+ LOCAL_INNER_6,
+ remoteOuter,
+ localOuter,
+ innerSocketPort,
+ 0,
+ i);
+ tunUtils.injectPacket(pkt);
+ receiveAndValidatePacket(socket);
+ }
+
+ final int innerPacketSize = getInnerPacketSize(AF_INET6, false);
+ checkTransformState(
+ inTunnelTransform,
+ 0L,
+ PACKET_COUNT,
+ PACKET_COUNT,
+ PACKET_COUNT * (long) innerPacketSize,
+ newReplayBitmap(REPLAY_BITMAP_LEN_BYTE * 8));
+
+ return innerSocketPort;
+ });
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ public void testRequestIpSecTransformStateForTx() throws Exception {
+ assumeRequestIpSecTransformStateSupported();
+
+ final int spi = getRandomSpi(LOCAL_OUTER_6, REMOTE_OUTER_6);
+ buildTunnelNetworkAndRunTestsSimple(
+ spi,
+ (ipsecNetwork,
+ tunnelIface,
+ tunUtils,
+ inTunnelTransform,
+ outTunnelTransform,
+ localOuter,
+ remoteOuter,
+ seqNum) -> {
+ // Build a socket and send traffic
+ final JavaUdpSocket outSocket = new JavaUdpSocket(LOCAL_INNER_6);
+ ipsecNetwork.bindSocket(outSocket.mSocket);
+ int innerSocketPort = outSocket.getPort();
+
+ int expectedPacketSize =
+ getPacketSize(
+ AF_INET6,
+ AF_INET6,
+ false /* useEncap */,
+ false /* transportInTunnelMode */);
+
+ for (int i = 0; i < PACKET_COUNT; i++) {
+ outSocket.sendTo(TEST_DATA, REMOTE_INNER_6, innerSocketPort);
+ tunUtils.awaitEspPacketNoPlaintext(
+ spi, TEST_DATA, false /* useEncap */, expectedPacketSize);
+ }
+
+ final int innerPacketSize =
+ getInnerPacketSize(AF_INET6, false /* transportInTunnelMode */);
+ checkTransformState(
+ outTunnelTransform,
+ PACKET_COUNT,
+ 0L,
+ PACKET_COUNT,
+ PACKET_COUNT * (long) innerPacketSize,
+ newReplayBitmap(0));
+
+ return innerSocketPort;
+ });
+ }
}
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..ff10e1a 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -19,19 +19,19 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static com.android.testutils.DevSdkIgnoreRuleKt.VANILLA_ICE_CREAM;
-
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
@@ -66,6 +66,7 @@
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.Rule;
@@ -511,30 +512,20 @@
assertArrayEquals(netCapabilities, nr.getCapabilities());
}
- @Test @IgnoreUpTo(VANILLA_ICE_CREAM)
+ // Default capabilities and default forbidden capabilities must not be changed on U- because
+ // this could cause the system server crash when there is a module rollback (b/313030307)
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R) @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testDefaultCapabilities() {
final NetworkRequest defaultNR = new NetworkRequest.Builder().build();
- assertTrue(defaultNR.hasForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK));
- assertFalse(defaultNR.hasCapability(NET_CAPABILITY_LOCAL_NETWORK));
+
+ assertEquals(4, defaultNR.getCapabilities().length);
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_TRUSTED));
assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VPN));
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
- final NetworkCapabilities emptyNC =
- NetworkCapabilities.Builder.withoutDefaultCapabilities().build();
- assertFalse(defaultNR.canBeSatisfiedBy(emptyNC));
-
- // defaultNC represent the capabilities of a network agent, so they must not contain
- // forbidden capabilities by default.
- final NetworkCapabilities defaultNC = new NetworkCapabilities.Builder().build();
- assertArrayEquals(new int[0], defaultNC.getForbiddenCapabilities());
- // A default NR can be satisfied by default NC.
- assertTrue(defaultNR.canBeSatisfiedBy(defaultNC));
-
- // Conversely, network requests have forbidden capabilities by default to manage
- // backward compatibility, so test that these forbidden capabilities are in place.
- // Starting in V, NET_CAPABILITY_LOCAL_NETWORK is introduced but is not seen by
- // default, thanks to a default forbidden capability in NetworkRequest.
- defaultNC.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
- assertFalse(defaultNR.canBeSatisfiedBy(defaultNC));
+ assertEquals(0, defaultNR.getForbiddenCapabilities().length);
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index 6a019b7..2315940 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -805,7 +805,7 @@
// harness, which is untagged, won't cause a failure.
long firstTotal = resultsWithTraffic.get(0).total;
for (QueryResult queryResult : resultsWithTraffic) {
- assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 12);
+ assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 16);
}
// Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
index 1b1f367..284fcae 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -28,7 +28,9 @@
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.NsdDiscoveryRecord
import com.android.testutils.TapPacketReader
+import com.android.testutils.pollForQuery
import com.android.testutils.tryTest
import java.util.Random
import kotlin.test.assertEquals
@@ -72,7 +74,7 @@
tryTest {
downstreamIface = createTestInterface()
- val iface = tetheredInterface
+ val iface = mTetheredInterfaceRequester.getInterface()
assertEquals(iface, downstreamIface?.interfaceName)
val request = TetheringRequest.Builder(TETHERING_ETHERNET)
.setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build()
@@ -115,7 +117,7 @@
tryTest {
downstreamIface = createTestInterface()
- val iface = tetheredInterface
+ val iface = mTetheredInterfaceRequester.getInterface()
assertEquals(iface, downstreamIface?.interfaceName)
val localAddr = LinkAddress("192.0.2.3/28")
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index ce2c2c1..be80787 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -39,19 +39,6 @@
import android.net.TestNetworkManager
import android.net.TestNetworkSpecifier
import android.net.connectivity.ConnectivityCompatChanges
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
-import android.net.cts.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
-import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
-import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
-import android.net.cts.NsdResolveRecord.ResolveEvent.ResolutionStopped
-import android.net.cts.NsdResolveRecord.ResolveEvent.ServiceResolved
-import android.net.cts.NsdResolveRecord.ResolveEvent.StopResolutionFailed
-import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
-import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
-import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
import android.net.cts.util.CtsNetUtils
import android.net.nsd.DiscoveryRequest
import android.net.nsd.NsdManager
@@ -78,9 +65,12 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.PollingCheck
import com.android.compatibility.common.util.PropertyUtil
+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
@@ -89,15 +79,41 @@
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.DeviceConfigRule
import com.android.testutils.NSResponder
+import com.android.testutils.NsdDiscoveryRecord
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
+import com.android.testutils.NsdEvent
+import com.android.testutils.NsdRecord
+import com.android.testutils.NsdRegistrationRecord
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
+import com.android.testutils.NsdResolveRecord
+import com.android.testutils.NsdResolveRecord.ResolveEvent.ResolutionStopped
+import com.android.testutils.NsdResolveRecord.ResolveEvent.ServiceResolved
+import com.android.testutils.NsdResolveRecord.ResolveEvent.StopResolutionFailed
+import com.android.testutils.NsdServiceInfoCallbackRecord
+import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
+import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
+import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.TapPacketReader
+import com.android.testutils.TestDnsPacket
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
+import com.android.testutils.pollForAdvertisement
+import com.android.testutils.pollForMdnsPacket
+import com.android.testutils.pollForProbe
+import com.android.testutils.pollForQuery
+import com.android.testutils.pollForReply
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
import com.android.testutils.waitForIdle
@@ -126,6 +142,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
@@ -137,6 +154,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
@@ -166,6 +186,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) }
@@ -1450,10 +1476,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)
@@ -1887,6 +1911,64 @@
}
}
+ @Test
+ fun testQueryWhenKnownAnswerSuppressionFlagSet() {
+ // The flag may be removed in the future but known-answer suppression should be enabled by
+ // default in that case. The rule will reset flags automatically on teardown.
+ deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_query_with_known_answer", "1")
+
+ // Register service on testNetwork1
+ val discoveryRecord = NsdDiscoveryRecord()
+ val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, { it.run() }, discoveryRecord)
+
+ tryTest {
+ discoveryRecord.expectCallback<DiscoveryStarted>()
+ assertNotNull(packetReader.pollForQuery("$serviceType.local", DnsResolver.TYPE_PTR))
+ /*
+ Generated with:
+ scapy.raw(scapy.DNS(rd=0, qr=1, aa=1, qd = None, an =
+ scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=120,
+ rdata='NsdTest123456789._nmt123456789._tcp.local'))).hex()
+ */
+ val ptrResponsePayload = HexDump.hexStringToByteArray("0000840000000001000000000d5f6e" +
+ "6d74313233343536373839045f746370056c6f63616c00000c000100000078002b104e736454" +
+ "6573743132333435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00")
+
+ replaceServiceNameAndTypeWithTestSuffix(ptrResponsePayload)
+ packetReader.sendResponse(buildMdnsPacket(ptrResponsePayload))
+
+ val serviceFound = discoveryRecord.expectCallback<ServiceFound>()
+ serviceFound.serviceInfo.let {
+ assertEquals(serviceName, it.serviceName)
+ // Discovered service types have a dot at the end
+ assertEquals("$serviceType.", it.serviceType)
+ assertEquals(testNetwork1.network, it.network)
+ // ServiceFound does not provide port, address or attributes (only information
+ // available in the PTR record is included in that callback, regardless of whether
+ // other records exist).
+ assertEquals(0, it.port)
+ assertEmpty(it.hostAddresses)
+ assertEquals(0, it.attributes.size)
+ }
+
+ // Expect the second query with a known answer
+ val query = packetReader.pollForMdnsPacket { pkt ->
+ pkt.isQueryFor("$serviceType.local", DnsResolver.TYPE_PTR) &&
+ pkt.isReplyFor("$serviceType.local", DnsResolver.TYPE_PTR)
+ }
+ assertNotNull(query)
+ } cleanup {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ }
+ }
+
private fun makeLinkLocalAddressOfOtherDeviceOnPrefix(network: Network): Inet6Address {
val lp = cm.getLinkProperties(network) ?: fail("No LinkProperties for net $network")
// Expect to have a /64 link-local address
@@ -1968,7 +2050,7 @@
}
@Test
- fun testAdvertisingAndDiscovery_multipleRegistrationsForSameCustomHost_unionOfAddressesFound() {
+ fun testAdvertisingAndDiscovery_multipleRegistrationsForSameCustomHost_hostRenamed() {
val hostAddresses1 = listOf(
parseNumericAddress("192.0.2.23"),
parseNumericAddress("2001:db8::1"),
@@ -1976,9 +2058,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
@@ -1992,18 +2071,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 {
@@ -2013,27 +2083,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)
@@ -2041,7 +2097,6 @@
} cleanup {
nsdManager.unregisterService(registrationRecord1)
nsdManager.unregisterService(registrationRecord2)
- nsdManager.unregisterService(registrationRecord3)
}
}
@@ -2106,6 +2161,343 @@
}
}
+ @Test
+ fun testRegisterService_registerImmediatelyAfterUnregister_serviceFound() {
+ val info1 = makeTestServiceInfo(network = testNetwork1.network).apply {
+ serviceName = "service11111"
+ port = 11111
+ }
+ val info2 = makeTestServiceInfo(network = testNetwork1.network).apply {
+ serviceName = "service22222"
+ port = 22222
+ }
+ val registrationRecord1 = NsdRegistrationRecord()
+ val discoveryRecord1 = NsdDiscoveryRecord()
+ val registrationRecord2 = NsdRegistrationRecord()
+ val discoveryRecord2 = NsdDiscoveryRecord()
+ tryTest {
+ registerService(registrationRecord1, info1)
+ nsdManager.discoverServices(serviceType,
+ NsdManager.PROTOCOL_DNS_SD, testNetwork1.network, { it.run() },
+ discoveryRecord1)
+ discoveryRecord1.waitForServiceDiscovered(info1.serviceName,
+ serviceType, testNetwork1.network)
+ nsdManager.stopServiceDiscovery(discoveryRecord1)
+
+ nsdManager.unregisterService(registrationRecord1)
+ registerService(registrationRecord2, info2)
+ nsdManager.discoverServices(serviceType,
+ NsdManager.PROTOCOL_DNS_SD, testNetwork1.network, { it.run() },
+ discoveryRecord2)
+ val infoDiscovered = discoveryRecord2.waitForServiceDiscovered(info2.serviceName,
+ serviceType, testNetwork1.network)
+ val infoResolved = resolveService(infoDiscovered)
+ assertEquals(22222, infoResolved.port)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord2)
+ discoveryRecord2.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord2)
+ }
+ }
+
+ @Test
+ fun testAdvertisingAndDiscovery_reregisterCustomHostWithDifferentAddresses_newAddressesFound() {
+ val si1 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.hostname = customHostname
+ it.hostAddresses = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"))
+ }
+ val si2 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceName = serviceName
+ it.serviceType = serviceType
+ it.hostname = customHostname
+ it.port = TEST_PORT
+ }
+ val si3 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.hostname = customHostname
+ it.hostAddresses = listOf(
+ parseNumericAddress("192.0.2.24"),
+ parseNumericAddress("2001:db8::2"))
+ }
+
+ val registrationRecord1 = NsdRegistrationRecord()
+ val registrationRecord2 = NsdRegistrationRecord()
+ val registrationRecord3 = NsdRegistrationRecord()
+
+ val discoveryRecord = NsdDiscoveryRecord()
+
+ tryTest {
+ registerService(registrationRecord1, si1)
+ registerService(registrationRecord2, si2)
+
+ nsdManager.unregisterService(registrationRecord1)
+ registrationRecord1.expectCallback<ServiceUnregistered>()
+
+ registerService(registrationRecord3, si3)
+
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord)
+ val discoveredInfo = discoveryRecord.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ val resolvedInfo = resolveService(discoveredInfo)
+
+ assertEquals(serviceName, discoveredInfo.serviceName)
+ assertEquals(TEST_PORT, resolvedInfo.port)
+ assertEquals(customHostname, resolvedInfo.hostname)
+ assertAddressEquals(
+ listOf(parseNumericAddress("192.0.2.24"), parseNumericAddress("2001:db8::2")),
+ resolvedInfo.hostAddresses)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallbackEventually<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord2)
+ nsdManager.unregisterService(registrationRecord3)
+ }
+ }
+
+ @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
+ val registrationRecord = NsdRegistrationRecord()
+ registerService(registrationRecord, si)
+ // Register multiple discovery requests.
+ val discoveryRecord1 = NsdDiscoveryRecord()
+ val discoveryRecord2 = NsdDiscoveryRecord()
+ val discoveryRecord3 = NsdDiscoveryRecord()
+ nsdManager.discoverServices("_test1._tcp", NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, { it.run() }, discoveryRecord1)
+ nsdManager.discoverServices("_test2._tcp", NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, { it.run() }, discoveryRecord2)
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord3)
+
+ tryTest {
+ discoveryRecord1.expectCallback<DiscoveryStarted>()
+ discoveryRecord2.expectCallback<DiscoveryStarted>()
+ discoveryRecord3.expectCallback<DiscoveryStarted>()
+ val foundInfo = discoveryRecord3.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ assertEquals(testNetwork1.network, foundInfo.network)
+ // Verify that associated ServiceTypeClients has been created for testNetwork1.
+ assertTrue("No serviceTypeClients for testNetwork1.",
+ hasServiceTypeClientsForNetwork(
+ getServiceTypeClients(), testNetwork1.network))
+
+ // Disconnect testNetwork1
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1.close(cm)
+ }
+
+ // Verify that no ServiceTypeClients for testNetwork1.
+ discoveryRecord3.expectCallback<ServiceLost>()
+ assertFalse("Still has serviceTypeClients for testNetwork1.",
+ hasServiceTypeClientsForNetwork(
+ getServiceTypeClients(), testNetwork1.network))
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord1)
+ nsdManager.stopServiceDiscovery(discoveryRecord2)
+ nsdManager.stopServiceDiscovery(discoveryRecord3)
+ discoveryRecord1.expectCallback<DiscoveryStopped>()
+ discoveryRecord2.expectCallback<DiscoveryStopped>()
+ discoveryRecord3.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
+ private fun hasServiceTypeClientsForNetwork(clients: List<String>, network: Network): Boolean {
+ return clients.any { client -> client.substring(
+ client.indexOf("network=") + "network=".length,
+ client.indexOf("interfaceIndex=") - 1) == network.getNetId().toString()
+ }
+ }
+
+ /**
+ * Get ServiceTypeClient logs from the system dump servicediscovery section.
+ *
+ * The sample output:
+ * ServiceTypeClient: Type{_nmt079019787._tcp.local} \
+ * SocketKey{ network=116 interfaceIndex=68 } with 1 listeners.
+ * ServiceTypeClient: Type{_nmt079019787._tcp.local} \
+ * SocketKey{ network=115 interfaceIndex=67 } with 1 listeners.
+ */
+ private fun getServiceTypeClients(): List<String> {
+ return SystemUtil.runShellCommand(
+ InstrumentationRegistry.getInstrumentation(), "dumpsys servicediscovery")
+ .split("\n").mapNotNull { line ->
+ line.indexOf("ServiceTypeClient:").let { idx ->
+ if (idx == -1) null
+ else line.substring(idx)
+ }
+ }
+ }
+
private fun buildConflictingAnnouncement(): ByteBuffer {
/*
Generated with:
@@ -2270,4 +2662,4 @@
// No duplicate addresses in the actual address list
assertEquals(actual.toSet().size, actual.size)
assertEquals(expected.toSet(), actual.toSet())
-}
\ No newline at end of file
+}
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/internetpermission/Android.bp b/tests/cts/netpermission/internetpermission/Android.bp
index 7d5ca2f..e0424ac 100644
--- a/tests/cts/netpermission/internetpermission/Android.bp
+++ b/tests/cts/netpermission/internetpermission/Android.bp
@@ -31,4 +31,5 @@
"general-tests",
],
host_required: ["net-tests-utils-host-common"],
+ sdk_version: "test_current",
}
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/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 3928961..1023173 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -47,6 +47,7 @@
// Change to system current when TetheringManager move to bootclass path.
platform_apis: true,
+ min_sdk_version: "30",
host_required: ["net-tests-utils-host-common"],
}
@@ -80,8 +81,8 @@
// Tethering CTS tests for development and release. These tests always target the platform SDK
// version, and are subject to all the restrictions appropriate to that version. Before SDK
-// finalization, these tests have a min_sdk_version of 10000, and cannot be installed on release
-// devices.
+// finalization, these tests have a min_sdk_version of 10000, but they can still be installed on
+// release devices as their min_sdk_version is set to a production version.
android_test {
name: "CtsTetheringTest",
defaults: ["CtsTetheringTestDefaults"],
@@ -93,6 +94,14 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
+ "mts-dnsresolver",
+ "mts-networking",
+ "mts-tethering",
+ "mts-wifi",
+ "mcts-dnsresolver",
+ "mcts-networking",
+ "mcts-tethering",
+ "mcts-wifi",
"general-tests",
],
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 361d68c..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
@@ -66,6 +67,8 @@
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.tryTest
+import java.util.function.BiConsumer
+import java.util.function.Consumer
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@@ -87,8 +90,6 @@
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
import org.mockito.Spy
-import java.util.function.Consumer
-import java.util.function.BiConsumer
const val SERVICE_BIND_TIMEOUT_MS = 5_000L
const val TEST_TIMEOUT_MS = 10_000L
@@ -225,6 +226,10 @@
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(
c: Context,
@@ -242,18 +247,19 @@
super.makeHandlerThread(tag).also { handlerThreads.add(it) }
override fun makeCarrierPrivilegeAuthenticator(
- context: Context,
- tm: TelephonyManager,
- requestRestrictedWifiEnabled: Boolean,
- listener: BiConsumer<Int, Int>
+ context: Context,
+ tm: TelephonyManager,
+ requestRestrictedWifiEnabled: Boolean,
+ listener: BiConsumer<Int, Int>,
+ handler: Handler
): CarrierPrivilegeAuthenticator {
return CarrierPrivilegeAuthenticator(context,
- object : CarrierPrivilegeAuthenticator.Dependencies() {
- override fun makeHandlerThread(): HandlerThread =
- super.makeHandlerThread().also { handlerThreads.add(it) }
- },
- tm, TelephonyManagerShimImpl.newInstance(tm),
- requestRestrictedWifiEnabled, listener)
+ object : CarrierPrivilegeAuthenticator.Dependencies() {
+ override fun makeHandlerThread(): HandlerThread =
+ super.makeHandlerThread().also { handlerThreads.add(it) }
+ },
+ tm, TelephonyManagerShimImpl.newInstance(tm),
+ requestRestrictedWifiEnabled, listener, handler)
}
override fun makeSatelliteAccessController(
diff --git a/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt b/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
index 104d063..3d948ba 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
@@ -18,10 +18,14 @@
import android.app.Service
import android.content.Intent
+import androidx.annotation.GuardedBy
+import com.android.testutils.quitExecutorServices
+import com.android.testutils.quitThreads
import java.net.URL
import java.util.Collections
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.concurrent.ExecutorService
import kotlin.collections.ArrayList
import kotlin.test.fail
@@ -37,7 +41,12 @@
.run {
withDefault { key -> getOrPut(key) { ConcurrentLinkedQueue() } }
}
- private val httpRequestUrls = Collections.synchronizedList(ArrayList<String>())
+ private val httpRequestUrls = Collections.synchronizedList(mutableListOf<String>())
+
+ @GuardedBy("networkMonitorThreads")
+ private val networkMonitorThreads = mutableListOf<Thread>()
+ @GuardedBy("networkMonitorExecutorServices")
+ private val networkMonitorExecutorServices = mutableListOf<ExecutorService>()
/**
* Called when an HTTP request is being processed by NetworkMonitor. Returns the response
@@ -52,10 +61,47 @@
}
/**
+ * Called when NetworkMonitor creates a new Thread.
+ */
+ fun onNetworkMonitorThreadCreated(thread: Thread) {
+ synchronized(networkMonitorThreads) {
+ networkMonitorThreads.add(thread)
+ }
+ }
+
+ /**
+ * Called when NetworkMonitor creates a new ExecutorService.
+ */
+ fun onNetworkMonitorExecutorServiceCreated(executorService: ExecutorService) {
+ synchronized(networkMonitorExecutorServices) {
+ networkMonitorExecutorServices.add(executorService)
+ }
+ }
+
+ /**
* Clear all state of this connector. This is intended for use between two tests, so all
* state should be reset as if the connector was just created.
*/
override fun clearAllState() {
+ quitThreads(
+ maxRetryCount = 3,
+ interrupt = true) {
+ synchronized(networkMonitorThreads) {
+ networkMonitorThreads.toList().also { networkMonitorThreads.clear() }
+ }
+ }
+ quitExecutorServices(
+ maxRetryCount = 3,
+ // NetworkMonitor is expected to have interrupted its executors when probing
+ // finishes, otherwise it's a thread pool leak that should be caught, so they should
+ // not need to be interrupted (the test only needs to wait for them to finish).
+ interrupt = false) {
+ synchronized(networkMonitorExecutorServices) {
+ networkMonitorExecutorServices.toList().also {
+ networkMonitorExecutorServices.clear()
+ }
+ }
+ }
httpResponses.clear()
httpRequestUrls.clear()
}
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 765e56e..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,
@@ -299,7 +308,8 @@
val buf = ByteArray(DEFAULT_BUFFER_SIZE)
httpServer.addResponse(
- TestHttpServer.Request(path, NanoHTTPD.Method.POST), NanoHTTPD.Response.Status.OK,
+ TestHttpServer.Request(path, NanoHTTPD.Method.POST),
+ NanoHTTPD.Response.Status.OK,
content = getRandomString(downloadSize)
)
var httpConnection: HttpURLConnection? = null
@@ -349,15 +359,19 @@
) {
operator fun plus(other: BareStats): BareStats {
return BareStats(
- this.rxBytes + other.rxBytes, this.rxPackets + other.rxPackets,
- this.txBytes + other.txBytes, this.txPackets + other.txPackets
+ this.rxBytes + other.rxBytes,
+ this.rxPackets + other.rxPackets,
+ this.txBytes + other.txBytes,
+ this.txPackets + other.txPackets
)
}
operator fun minus(other: BareStats): BareStats {
return BareStats(
- this.rxBytes - other.rxBytes, this.rxPackets - other.rxPackets,
- this.txBytes - other.txBytes, this.txPackets - other.txPackets
+ this.rxBytes - other.rxBytes,
+ this.rxPackets - other.rxPackets,
+ this.txBytes - other.txBytes,
+ this.txPackets - other.txPackets
)
}
@@ -405,8 +419,12 @@
private fun getUidDetail(iface: String, tag: Int): BareStats {
return getNetworkStatsThat(iface, tag) { nsm, template ->
nsm.queryDetailsForUidTagState(
- template, Long.MIN_VALUE, Long.MAX_VALUE,
- Process.myUid(), tag, Bucket.STATE_ALL
+ template,
+ Long.MIN_VALUE,
+ Long.MAX_VALUE,
+ Process.myUid(),
+ tag,
+ Bucket.STATE_ALL
)
}
}
@@ -498,28 +516,36 @@
assertInRange(
"Unexpected iface traffic stats",
after.iface,
- before.trafficStatsIface, after.trafficStatsIface,
- lower, upper
+ before.trafficStatsIface,
+ after.trafficStatsIface,
+ lower,
+ upper
)
// Uid traffic stats are counted in both direction because the external network
// traffic is also attributed to the test uid.
assertInRange(
"Unexpected uid traffic stats",
after.iface,
- before.trafficStatsUid, after.trafficStatsUid,
- lower + lower.reverse(), upper + upper.reverse()
+ before.trafficStatsUid,
+ after.trafficStatsUid,
+ lower + lower.reverse(),
+ upper + upper.reverse()
)
assertInRange(
"Unexpected non-tagged summary stats",
after.iface,
- before.statsSummary, after.statsSummary,
- lower, upper
+ before.statsSummary,
+ after.statsSummary,
+ lower,
+ upper
)
assertInRange(
"Unexpected non-tagged uid stats",
after.iface,
- before.statsUid, after.statsUid,
- lower, upper
+ before.statsUid,
+ after.statsUid,
+ lower,
+ upper
)
}
@@ -546,14 +572,16 @@
assertInRange(
"Unexpected tagged summary stats",
after.iface,
- before.taggedSummary, after.taggedSummary,
+ before.taggedSummary,
+ after.taggedSummary,
lower,
upper
)
assertInRange(
"Unexpected tagged uid stats: ${Process.myUid()}",
after.iface,
- before.taggedUid, after.taggedUid,
+ before.taggedUid,
+ after.taggedUid,
lower,
upper
)
@@ -570,7 +598,8 @@
) {
// Passing the value after operation and the value before operation to dump the actual
// numbers if it fails.
- assertTrue(checkInRange(before, after, lower, upper),
+ assertTrue(
+ checkInRange(before, after, lower, upper),
"$tag on $iface: $after - $before is not within range [$lower, $upper]"
)
}
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ServiceManagerWrapperIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ServiceManagerWrapperIntegrationTest.kt
new file mode 100644
index 0000000..7e00ed2
--- /dev/null
+++ b/tests/integration/src/com/android/server/net/integrationtests/ServiceManagerWrapperIntegrationTest.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.net.integrationtests
+
+import android.content.Context
+import android.os.Build
+import com.android.server.ServiceManagerWrapper
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertNotNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Integration tests for {@link ServiceManagerWrapper}. */
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.S)
+@ConnectivityModuleTest
+class ServiceManagerWrapperIntegrationTest {
+ @Test
+ fun testWaitForService_successFullyRetrievesConnectivityServiceBinder() {
+ assertNotNull(ServiceManagerWrapper.waitForService(Context.CONNECTIVITY_SERVICE))
+ }
+}
diff --git a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index 7e227c4..e43ce29 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -30,13 +30,14 @@
import com.android.server.NetworkStackService.NetworkStackConnector
import com.android.server.connectivity.NetworkMonitor
import com.android.server.net.integrationtests.NetworkStackInstrumentationService.InstrumentationConnector
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
import java.io.ByteArrayInputStream
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
+import java.util.concurrent.ExecutorService
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
private const val TEST_NETID = 42
@@ -60,6 +61,10 @@
private class NetworkMonitorDeps(private val privateDnsBypassNetwork: Network) :
NetworkMonitor.Dependencies() {
override fun getPrivateDnsBypassNetwork(network: Network?) = privateDnsBypassNetwork
+ override fun onThreadCreated(thread: Thread) =
+ InstrumentationConnector.onNetworkMonitorThreadCreated(thread)
+ override fun onExecutorServiceCreated(ecs: ExecutorService) =
+ InstrumentationConnector.onNetworkMonitorExecutorServiceCreated(ecs)
}
/**
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/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 51a4eca..29f5cd2 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -68,6 +68,8 @@
TETHERING "map_offload_tether_upstream6_map",
TETHERING "map_test_bitmap",
TETHERING "map_test_tether_downstream6_map",
+ TETHERING "map_test_tether2_downstream6_map",
+ TETHERING "map_test_tether3_downstream6_map",
TETHERING "prog_offload_schedcls_tether_downstream4_ether",
TETHERING "prog_offload_schedcls_tether_downstream4_rawip",
TETHERING "prog_offload_schedcls_tether_downstream6_ether",
@@ -141,6 +143,27 @@
NETD "map_netd_packet_trace_ringbuf",
};
+// Provided by *current* mainline module for V+ devices
+static const set<string> MAINLINE_FOR_V_PLUS = {
+ NETD "prog_netd_connect4_inet4_connect",
+ NETD "prog_netd_connect6_inet6_connect",
+ NETD "prog_netd_recvmsg4_udp4_recvmsg",
+ NETD "prog_netd_recvmsg6_udp6_recvmsg",
+ NETD "prog_netd_sendmsg4_udp4_sendmsg",
+ NETD "prog_netd_sendmsg6_udp6_sendmsg",
+};
+
+// Provided by *current* mainline module for V+ devices with 5.4+ kernels
+static const set<string> MAINLINE_FOR_V_5_4_PLUS = {
+ NETD "prog_netd_getsockopt_prog",
+ NETD "prog_netd_setsockopt_prog",
+};
+
+// Provided by *current* mainline module for U+ devices with 5.10+ kernels
+static const set<string> MAINLINE_FOR_V_5_10_PLUS = {
+ NETD "prog_netd_cgroupsockrelease_inet_release",
+};
+
static void addAll(set<string>& a, const set<string>& b) {
a.insert(b.begin(), b.end());
}
@@ -188,6 +211,9 @@
// V requires Linux Kernel 4.19+, but nothing (as yet) added or removed in V.
if (IsAtLeastV()) ASSERT_TRUE(isAtLeastKernelVersion(4, 19, 0));
+ DO_EXPECT(IsAtLeastV(), MAINLINE_FOR_V_PLUS);
+ DO_EXPECT(IsAtLeastV() && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_V_5_4_PLUS);
+ DO_EXPECT(IsAtLeastV() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_V_5_10_PLUS);
for (const auto& file : mustExist) {
EXPECT_EQ(0, access(file.c_str(), R_OK)) << file << " does not exist";
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/native/utilities/firewall.h b/tests/native/utilities/firewall.h
index b3d69bf..a5cb0b9 100644
--- a/tests/native/utilities/firewall.h
+++ b/tests/native/utilities/firewall.h
@@ -18,6 +18,7 @@
#pragma once
#include <android-base/thread_annotations.h>
+#define BPF_MAP_LOCKLESS_FOR_TEST
#include <bpf/BpfMap.h>
#include "netd.h"
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 20d457f..2f88c41 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -58,14 +58,11 @@
filegroup {
name: "non-connectivity-module-test",
srcs: [
- "java/android/net/Ikev2VpnProfileTest.java",
"java/android/net/IpMemoryStoreTest.java",
"java/android/net/TelephonyNetworkSpecifierTest.java",
- "java/android/net/VpnManagerTest.java",
"java/android/net/ipmemorystore/*.java",
"java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
"java/com/android/internal/net/NetworkUtilsInternalTest.java",
- "java/com/android/internal/net/VpnProfileTest.java",
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
"java/com/android/server/connectivity/IpConnectivityMetricsTest.java",
"java/com/android/server/connectivity/MetricsTestUtil.java",
@@ -75,17 +72,6 @@
],
}
-// Subset of services-core used to by ConnectivityService tests to test VPN realistically.
-// This is stripped by jarjar (see rules below) from other unrelated classes, so tests do not
-// include most classes from services-core, which are unrelated and cause wrong code coverage
-// calculations.
-java_library {
- name: "services.core-vpn",
- static_libs: ["services.core"],
- jarjar_rules: "vpn-jarjar-rules.txt",
- visibility: ["//visibility:private"],
-}
-
java_defaults {
name: "FrameworksNetTestsDefaults",
min_sdk_version: "30",
@@ -112,7 +98,6 @@
"platform-test-annotations",
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
- "services.core-vpn",
"testables",
"cts-net-utils",
],
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index b71a46f..9a77c89 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -52,6 +52,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -72,6 +73,7 @@
import android.os.Messenger;
import android.os.Process;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.BroadcastInterceptingContext;
@@ -83,6 +85,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -240,7 +243,7 @@
// register callback
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(request);
manager.requestNetwork(request, callback, handler);
// callback triggers
@@ -269,7 +272,7 @@
// register callback
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(req1);
manager.requestNetwork(req1, callback, handler);
// callback triggers
@@ -287,7 +290,7 @@
// callback can be registered again
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(req2);
manager.requestNetwork(req2, callback, handler);
// callback triggers
@@ -311,7 +314,7 @@
when(mCtx.getApplicationInfo()).thenReturn(info);
when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(),
- anyInt(), any(), nullable(String.class))).thenReturn(request);
+ anyInt(), any(), nullable(String.class), anyInt())).thenReturn(request);
Handler handler = new Handler(Looper.getMainLooper());
manager.requestNetwork(request, callback, handler);
@@ -403,15 +406,15 @@
manager.requestNetwork(request, callback);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
// Verify that register network callback does not calls requestNetwork at all.
manager.registerNetworkCallback(request, callback);
verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(),
- anyInt(), anyInt(), any(), any());
+ anyInt(), anyInt(), any(), any(), anyInt());
verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
@@ -419,24 +422,24 @@
manager.registerDefaultNetworkCallback(callback);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
manager.registerDefaultNetworkCallbackForUid(42, callback, handler);
verify(mService).requestNetwork(eq(42), eq(null),
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
manager.requestBackgroundNetwork(request, callback, handler);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
manager.registerSystemDefaultNetworkCallback(callback, handler);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
}
@@ -516,16 +519,154 @@
+ " attempts", ref.get());
}
- private <T> void mockService(Class<T> clazz, String name, T service) {
- doReturn(service).when(mCtx).getSystemService(name);
- doReturn(name).when(mCtx).getSystemServiceName(clazz);
+ @Test
+ public void testDeclaredMethodsFlag_requestWithMixedMethods_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
- // If the test suite uses the inline mock maker library, such as for coverage tests,
- // then the final version of getSystemService must also be mocked, as the real
- // method will not be called by the test and null object is returned since no mock.
- // Otherwise, mocking a final method will fail the test.
- if (mCtx.getSystemService(clazz) == null) {
- doReturn(service).when(mCtx).getSystemService(clazz);
- }
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkCallback callback1 = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onPreCheck(@NonNull Network network) {}
+ @Override
+ public void onAvailable(@NonNull Network network) {}
+ @Override
+ public void onLost(@NonNull Network network) {}
+ @Override
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities) {}
+ @Override
+ public void onLocalNetworkInfoChanged(@NonNull Network network,
+ @NonNull LocalNetworkInfo localNetworkInfo) {}
+ @Override
+ public void onNetworkResumed(@NonNull Network network) {}
+ @Override
+ public void onBlockedStatusChanged(@NonNull Network network, int blocked) {}
+ };
+ manager.requestNetwork(request, callback1);
+
+ final InOrder inOrder = inOrder(mService);
+ inOrder.verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_PRECHECK
+ | 1 << ConnectivityManager.CALLBACK_AVAILABLE
+ | 1 << ConnectivityManager.CALLBACK_LOST
+ | 1 << ConnectivityManager.CALLBACK_CAP_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_RESUMED
+ | 1 << ConnectivityManager.CALLBACK_BLK_CHANGED));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_listenWithMixedMethods_RegistrationFlagsMatch()
+ throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ final NetworkCallback callback2 = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onLosing(@NonNull Network network, int maxMsToLive) {}
+ @Override
+ public void onUnavailable() {}
+ @Override
+ public void onLinkPropertiesChanged(@NonNull Network network,
+ @NonNull LinkProperties linkProperties) {}
+ @Override
+ public void onNetworkSuspended(@NonNull Network network) {}
+ };
+ manager.registerNetworkCallback(request, callback2);
+ // Call a second time with the same callback to exercise caching
+ manager.registerNetworkCallback(request, callback2);
+
+ verify(mService, times(2)).listenForNetwork(
+ any(), any(), any(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_LOSING
+ // AVAILABLE calls IP_CHANGED and SUSPENDED so it gets added
+ | 1 << ConnectivityManager.CALLBACK_AVAILABLE
+ | 1 << ConnectivityManager.CALLBACK_UNAVAIL
+ | 1 << ConnectivityManager.CALLBACK_IP_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_SUSPENDED));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWithHiddenAvailableCallback_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ final NetworkCallback hiddenOnAvailableCb = new ConnectivityManager.NetworkCallback() {
+ // This overload is @hide but might still be used by (bad) apps
+ @Override
+ public void onAvailable(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull LinkProperties linkProperties, boolean blocked) {}
+ };
+ manager.registerDefaultNetworkCallback(hiddenOnAvailableCb);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_AVAILABLE));
+ }
+
+ public static class NetworkCallbackWithOnLostOnly extends NetworkCallback {
+ @Override
+ public void onLost(@NonNull Network network) {}
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWithoutAvailableCallback_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkCallback noOnAvailableCb = new NetworkCallbackWithOnLostOnly();
+ manager.registerSystemDefaultNetworkCallback(noOnAvailableCb, handler);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_LOST));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_listenWithMock_OptimizationDisabled()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ manager.registerNetworkCallback(request, mock(NetworkCallbackWithOnLostOnly.class),
+ handler);
+
+ verify(mService).listenForNetwork(
+ any(), any(), any(), anyInt(), any(), any(),
+ // Mock that does not call the constructor -> do not use the optimization
+ eq(~0));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWitNoCallback_OptimizationDisabled()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkCallback noCallbackAtAll = new ConnectivityManager.NetworkCallback() {};
+ manager.requestBackgroundNetwork(request, noCallbackAtAll, handler);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ // No callbacks overridden -> do not use the optimization
+ eq(~0));
}
}
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
deleted file mode 100644
index e12e961..0000000
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ /dev/null
@@ -1,585 +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;
-
-import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
-import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6;
-import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
-
-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.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.net.ipsec.ike.IkeKeyIdIdentification;
-import android.net.ipsec.ike.IkeTunnelConnectionParams;
-import android.os.Build;
-import android.test.mock.MockContext;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.net.VpnProfile;
-import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
-import com.android.net.module.util.ProxyUtils;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.security.auth.x500.X500Principal;
-
-/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class Ikev2VpnProfileTest {
- private static final String SERVER_ADDR_STRING = "1.2.3.4";
- private static final String IDENTITY_STRING = "Identity";
- private static final String USERNAME_STRING = "username";
- private static final String PASSWORD_STRING = "pa55w0rd";
- private static final String EXCL_LIST = "exclList";
- private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
- private static final int TEST_MTU = 1300;
-
- @Rule
- public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
-
- private final MockContext mMockContext =
- new MockContext() {
- @Override
- public String getOpPackageName() {
- return "fooPackage";
- }
- };
- private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy(
- SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST));
-
- private X509Certificate mUserCert;
- private X509Certificate mServerRootCa;
- private PrivateKey mPrivateKey;
-
- @Before
- public void setUp() throws Exception {
- mServerRootCa = generateRandomCertAndKeyPair().cert;
-
- final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
- mUserCert = userCertKey.cert;
- mPrivateKey = userCertKey.key;
- }
-
- private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
- final Ikev2VpnProfile.Builder builder =
- new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
-
- builder.setBypassable(true);
- builder.setProxy(mProxy);
- builder.setMaxMtu(TEST_MTU);
- builder.setMetered(true);
-
- return builder;
- }
-
- @Test
- public void testBuildValidProfileWithOptions() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
-
- // Check non-auth parameters correctly stored
- assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
- assertEquals(IDENTITY_STRING, profile.getUserIdentity());
- assertEquals(mProxy, profile.getProxyInfo());
- assertTrue(profile.isBypassable());
- assertTrue(profile.isMetered());
- assertEquals(TEST_MTU, profile.getMaxMtu());
- assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms());
- }
-
- @Test
- public void testBuildUsernamePasswordProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
-
- assertEquals(USERNAME_STRING, profile.getUsername());
- assertEquals(PASSWORD_STRING, profile.getPassword());
- assertEquals(mServerRootCa, profile.getServerRootCaCert());
-
- assertNull(profile.getPresharedKey());
- assertNull(profile.getRsaPrivateKey());
- assertNull(profile.getUserCert());
- }
-
- @Test
- public void testBuildDigitalSignatureProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
-
- assertEquals(profile.getUserCert(), mUserCert);
- assertEquals(mPrivateKey, profile.getRsaPrivateKey());
- assertEquals(profile.getServerRootCaCert(), mServerRootCa);
-
- assertNull(profile.getPresharedKey());
- assertNull(profile.getUsername());
- assertNull(profile.getPassword());
- }
-
- @Test
- public void testBuildPresharedKeyProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthPsk(PSK_BYTES);
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
-
- assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
-
- assertNull(profile.getServerRootCaCert());
- assertNull(profile.getUsername());
- assertNull(profile.getPassword());
- assertNull(profile.getRsaPrivateKey());
- assertNull(profile.getUserCert());
- }
-
- @Test
- public void testBuildWithAllowedAlgorithmsAead() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAuthPsk(PSK_BYTES);
-
- List<String> allowedAlgorithms =
- Arrays.asList(
- IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
- IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305);
- builder.setAllowedAlgorithms(allowedAlgorithms);
-
- final Ikev2VpnProfile profile = builder.build();
- assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
- }
-
- @Test
- public void testBuildWithAllowedAlgorithmsNormal() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAuthPsk(PSK_BYTES);
-
- List<String> allowedAlgorithms =
- Arrays.asList(
- IpSecAlgorithm.AUTH_HMAC_SHA512,
- IpSecAlgorithm.AUTH_AES_XCBC,
- IpSecAlgorithm.AUTH_AES_CMAC,
- IpSecAlgorithm.CRYPT_AES_CBC,
- IpSecAlgorithm.CRYPT_AES_CTR);
- builder.setAllowedAlgorithms(allowedAlgorithms);
-
- final Ikev2VpnProfile profile = builder.build();
- assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
- }
-
- @Test
- public void testSetAllowedAlgorithmsEmptyList() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- try {
- builder.setAllowedAlgorithms(new ArrayList<>());
- fail("Expected exception due to no valid algorithm set");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testSetAllowedAlgorithmsInvalidList() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- List<String> allowedAlgorithms = new ArrayList<>();
-
- try {
- builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256));
- fail("Expected exception due to missing encryption");
- } catch (IllegalArgumentException expected) {
- }
-
- try {
- builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC));
- fail("Expected exception due to missing authentication");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- List<String> allowedAlgorithms = new ArrayList<>();
-
- try {
- builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5));
- fail("Expected exception due to insecure algorithm");
- } catch (IllegalArgumentException expected) {
- }
-
- try {
- builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1));
- fail("Expected exception due to insecure algorithm");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testBuildNoAuthMethodSet() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- try {
- builder.build();
- fail("Expected exception due to lack of auth method");
- } catch (IllegalArgumentException expected) {
- }
- }
-
-
- // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP and mainline branch
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
- @Test
- public void testBuildExcludeLocalRoutesSet() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAuthPsk(PSK_BYTES);
- builder.setLocalRoutesExcluded(true);
-
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
- assertTrue(profile.areLocalRoutesExcluded());
-
- builder.setBypassable(false);
- try {
- builder.build();
- fail("Expected exception because excludeLocalRoutes should be set only"
- + " on the bypassable VPN");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testBuildInvalidMtu() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- try {
- builder.setMaxMtu(500);
- fail("Expected exception due to too-small MTU");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- private void verifyVpnProfileCommon(VpnProfile profile) {
- assertEquals(SERVER_ADDR_STRING, profile.server);
- assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
- assertEquals(mProxy, profile.proxy);
- assertTrue(profile.isBypassable);
- assertTrue(profile.isMetered);
- assertEquals(TEST_MTU, profile.maxMtu);
- }
-
- @Test
- public void testPskConvertToVpnProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthPsk(PSK_BYTES);
- final VpnProfile profile = builder.build().toVpnProfile();
-
- verifyVpnProfileCommon(profile);
- assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
-
- // Check nothing else is set
- assertEquals("", profile.username);
- assertEquals("", profile.password);
- assertEquals("", profile.ipsecUserCert);
- assertEquals("", profile.ipsecCaCert);
- }
-
- @Test
- public void testUsernamePasswordConvertToVpnProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final VpnProfile profile = builder.build().toVpnProfile();
-
- verifyVpnProfileCommon(profile);
- assertEquals(USERNAME_STRING, profile.username);
- assertEquals(PASSWORD_STRING, profile.password);
- assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
-
- // Check nothing else is set
- assertEquals("", profile.ipsecUserCert);
- assertEquals("", profile.ipsecSecret);
- }
-
- @Test
- public void testRsaConvertToVpnProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final VpnProfile profile = builder.build().toVpnProfile();
-
- final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
- + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
- verifyVpnProfileCommon(profile);
- assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
- assertEquals(
- expectedSecret,
- profile.ipsecSecret);
- assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
-
- // Check nothing else is set
- assertEquals("", profile.username);
- assertEquals("", profile.password);
- }
-
- @Test
- public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthPsk(PSK_BYTES);
- final VpnProfile profile = builder.build().toVpnProfile();
- profile.username = USERNAME_STRING;
- profile.password = PASSWORD_STRING;
- profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
- profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
-
- final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
- assertNull(result.getUsername());
- assertNull(result.getPassword());
- assertNull(result.getUserCert());
- assertNull(result.getRsaPrivateKey());
- assertNull(result.getServerRootCaCert());
- }
-
- @Test
- public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final VpnProfile profile = builder.build().toVpnProfile();
- profile.ipsecSecret = new String(PSK_BYTES);
- profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
-
- final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
- assertNull(result.getPresharedKey());
- assertNull(result.getUserCert());
- assertNull(result.getRsaPrivateKey());
- }
-
- @Test
- public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final VpnProfile profile = builder.build().toVpnProfile();
- profile.username = USERNAME_STRING;
- profile.password = PASSWORD_STRING;
-
- final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
- assertNull(result.getUsername());
- assertNull(result.getPassword());
- assertNull(result.getPresharedKey());
- }
-
- @Test
- public void testPskConversionIsLossless() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthPsk(PSK_BYTES);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testUsernamePasswordConversionIsLossless() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testRsaConversionIsLossless() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testBuildWithIkeTunConnParamsConvertToVpnProfile() throws Exception {
- // Special keyId that contains delimiter character of VpnProfile
- final byte[] keyId = "foo\0bar".getBytes();
- final IkeTunnelConnectionParams tunnelParams = new IkeTunnelConnectionParams(
- getTestIkeSessionParams(true /* testIpv6 */, new IkeKeyIdIdentification(keyId)),
- CHILD_PARAMS);
- final Ikev2VpnProfile ikev2VpnProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
- final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
-
- assertEquals(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS, vpnProfile.type);
-
- // Username, password, server, ipsecIdentifier, ipsecCaCert, ipsecSecret, ipsecUserCert and
- // getAllowedAlgorithms should not be set if IkeTunnelConnectionParams is set.
- assertEquals("", vpnProfile.server);
- assertEquals("", vpnProfile.ipsecIdentifier);
- assertEquals("", vpnProfile.username);
- assertEquals("", vpnProfile.password);
- assertEquals("", vpnProfile.ipsecCaCert);
- assertEquals("", vpnProfile.ipsecSecret);
- assertEquals("", vpnProfile.ipsecUserCert);
- assertEquals(0, vpnProfile.getAllowedAlgorithms().size());
-
- // IkeTunnelConnectionParams should stay the same.
- assertEquals(tunnelParams, vpnProfile.ikeTunConnParams);
-
- // Convert to disk-stable format and then back to Ikev2VpnProfile should be the same.
- final VpnProfile decodedVpnProfile =
- VpnProfile.decode(vpnProfile.key, vpnProfile.encode());
- final Ikev2VpnProfile convertedIkev2VpnProfile =
- Ikev2VpnProfile.fromVpnProfile(decodedVpnProfile);
- assertEquals(ikev2VpnProfile, convertedIkev2VpnProfile);
- }
-
- @Test
- public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
- final IkeTunnelConnectionParams tunnelParams =
- new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
- // Config authentication related fields is not required while building with
- // IkeTunnelConnectionParams.
- final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAutomaticNattKeepaliveTimerEnabled(true);
- builder.setAutomaticIpVersionSelectionEnabled(true);
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testAutomaticNattAndIpVersionDefaults() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled());
- assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled());
- }
-
- @Test
- public void testEquals() throws Exception {
- // Verify building without IkeTunnelConnectionParams
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- assertEquals(builder.build(), builder.build());
-
- // Verify building with IkeTunnelConnectionParams
- final IkeTunnelConnectionParams tunnelParams =
- new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
- final IkeTunnelConnectionParams tunnelParams2 =
- new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
- assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
- new Ikev2VpnProfile.Builder(tunnelParams2).build());
- }
-
- @Test
- public void testBuildProfileWithNullProxy() throws Exception {
- final Ikev2VpnProfile ikev2VpnProfile =
- new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
- .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa)
- .build();
-
- // ProxyInfo should be null for the profile without setting ProxyInfo.
- assertNull(ikev2VpnProfile.getProxyInfo());
-
- // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile()
- final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
- assertNull(vpnProfile.proxy);
-
- final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile);
- assertNull(convertedIkev2VpnProfile.getProxyInfo());
- }
-
- private static class CertificateAndKey {
- public final X509Certificate cert;
- public final PrivateKey key;
-
- CertificateAndKey(X509Certificate cert, PrivateKey key) {
- this.cert = cert;
- this.key = key;
- }
- }
-
- private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
- final Date validityBeginDate =
- new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
- final Date validityEndDate =
- new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
-
- // Generate a keypair
- final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
- keyPairGenerator.initialize(512);
- final KeyPair keyPair = keyPairGenerator.generateKeyPair();
-
- final X500Principal dnName = new X500Principal("CN=test.android.com");
- final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
- certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
- certGen.setSubjectDN(dnName);
- certGen.setIssuerDN(dnName);
- certGen.setNotBefore(validityBeginDate);
- certGen.setNotAfter(validityEndDate);
- certGen.setPublicKey(keyPair.getPublic());
- certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
-
- final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
- return new CertificateAndKey(cert, keyPair.getPrivate());
- }
-}
diff --git a/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt b/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt
new file mode 100644
index 0000000..af06a64
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt
@@ -0,0 +1,212 @@
+/*
+ * 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 android.net
+
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.ConnectivityManager.NetworkCallbackMethodsHolder
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doCallRealMethod
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.mockingDetails
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner::class)
+class NetworkCallbackFlagsTest {
+
+ // To avoid developers forgetting to update NETWORK_CB_METHODS when modifying NetworkCallbacks,
+ // or using wrong values, calculate it from annotations here and verify that it matches.
+ // This avoids the runtime cost of reflection, but still ensures that the list is correct.
+ @Test
+ fun testNetworkCallbackMethods_calculateFromAnnotations_matchesHardcodedList() {
+ val calculatedMethods = getNetworkCallbackMethodsFromAnnotations()
+ assertEquals(
+ calculatedMethods.toSet(),
+ NetworkCallbackMethodsHolder.NETWORK_CB_METHODS.map {
+ NetworkCallbackMethodWithEquals(
+ it.mName,
+ it.mParameterTypes.toList(),
+ callbacksCallingThisMethod = it.mCallbacksCallingThisMethod
+ )
+ }.toSet()
+ )
+ }
+
+ data class NetworkCallbackMethodWithEquals(
+ val name: String,
+ val parameterTypes: List<Class<*>>,
+ val callbacksCallingThisMethod: Int
+ )
+
+ data class NetworkCallbackMethodBuilder(
+ val name: String,
+ val parameterTypes: List<Class<*>>,
+ val isFinal: Boolean,
+ val methodId: Int,
+ val mayCall: Set<Int>?,
+ var callbacksCallingThisMethod: Int
+ ) {
+ fun build() = NetworkCallbackMethodWithEquals(
+ name,
+ parameterTypes,
+ callbacksCallingThisMethod
+ )
+ }
+
+ /**
+ * Build [NetworkCallbackMethodsHolder.NETWORK_CB_METHODS] from [NetworkCallback] annotations.
+ */
+ private fun getNetworkCallbackMethodsFromAnnotations(): List<NetworkCallbackMethodWithEquals> {
+ val parsedMethods = mutableListOf<NetworkCallbackMethodBuilder>()
+ val methods = NetworkCallback::class.java.declaredMethods
+ methods.forEach { method ->
+ val cb = method.getAnnotation(
+ NetworkCallback.FilteredCallback::class.java
+ ) ?: return@forEach
+ val callbacksCallingThisMethod = if (cb.calledByCallbackId == 0) {
+ 0
+ } else {
+ 1 shl cb.calledByCallbackId
+ }
+ parsedMethods.add(
+ NetworkCallbackMethodBuilder(
+ method.name,
+ method.parameterTypes.toList(),
+ Modifier.isFinal(method.modifiers),
+ cb.methodId,
+ cb.mayCall.toSet(),
+ callbacksCallingThisMethod
+ )
+ )
+ }
+
+ // Propagate callbacksCallingThisMethod for transitive calls
+ do {
+ var hadChange = false
+ parsedMethods.forEach { caller ->
+ parsedMethods.forEach { callee ->
+ if (caller.mayCall?.contains(callee.methodId) == true) {
+ // Callbacks that call the caller also cause calls to the callee. So
+ // callbacksCallingThisMethod for the callee should include
+ // callbacksCallingThisMethod from the caller.
+ val newValue =
+ caller.callbacksCallingThisMethod or callee.callbacksCallingThisMethod
+ hadChange = hadChange || callee.callbacksCallingThisMethod != newValue
+ callee.callbacksCallingThisMethod = newValue
+ }
+ }
+ }
+ } while (hadChange)
+
+ // Final methods may affect the flags for transitive calls, but cannot be overridden, so do
+ // not need to be in the list (no overridden method in NetworkCallback will match them).
+ return parsedMethods.filter { !it.isFinal }.map { it.build() }
+ }
+
+ @Test
+ fun testMethodsAreAnnotated() {
+ val annotations = NetworkCallback::class.java.declaredMethods.mapNotNull { method ->
+ if (!Modifier.isPublic(method.modifiers) && !Modifier.isProtected(method.modifiers)) {
+ return@mapNotNull null
+ }
+ val annotation = method.getAnnotation(NetworkCallback.FilteredCallback::class.java)
+ assertNotNull(annotation, "$method is missing the @FilteredCallback annotation")
+ return@mapNotNull annotation
+ }
+
+ annotations.groupingBy { it.methodId }.eachCount().forEach { (methodId, cnt) ->
+ assertEquals(1, cnt, "Method ID $methodId is used more than once in @FilteredCallback")
+ }
+ }
+
+ @Test
+ fun testObviousCalleesAreInAnnotation() {
+ NetworkCallback::class.java.declaredMethods.forEach { method ->
+ val annotation = method.getAnnotation(NetworkCallback.FilteredCallback::class.java)
+ ?: return@forEach
+ val missingFlags = getObviousCallees(method).toMutableSet().apply {
+ removeAll(annotation.mayCall.toSet())
+ }
+ val msg = "@FilteredCallback on $method is missing flags " +
+ "$missingFlags in mayCall. There may be other " +
+ "calls that are not detected if they are done conditionally."
+ assertEquals(emptySet(), missingFlags, msg)
+ }
+ }
+
+ /**
+ * Invoke the specified NetworkCallback method with mock arguments, return a set of transitively
+ * called methods.
+ *
+ * This provides an idea of which methods are transitively called by the specified method. It's
+ * not perfect as some callees could be called or not depending on the exact values of the mock
+ * arguments that are passed in (for example, onAvailable calls onNetworkSuspended only if the
+ * capabilities lack the NOT_SUSPENDED capability), but it should catch obvious forgotten calls.
+ */
+ private fun getObviousCallees(method: Method): Set<Int> {
+ // Create a mock NetworkCallback that mocks all methods except the one specified by the
+ // caller.
+ val mockCallback = mock(NetworkCallback::class.java)
+
+ if (!Modifier.isFinal(method.modifiers) ||
+ // The mock class will be NetworkCallback (not a subclass) if using mockito-inline,
+ // which mocks final methods too
+ mockCallback.javaClass == NetworkCallback::class.java) {
+ doCallRealMethod().`when`(mockCallback).let { mockObj ->
+ val anyArgs = method.parameterTypes.map { any(it) }
+ method.invoke(mockObj, *anyArgs.toTypedArray())
+ }
+ }
+
+ // Invoke the target method with mock parameters
+ val mockParameters = method.parameterTypes.map { getMockFor(method, it) }
+ method.invoke(mockCallback, *mockParameters.toTypedArray())
+
+ // Aggregate callees
+ val mockingDetails = mockingDetails(mockCallback)
+ return mockingDetails.invocations.mapNotNull { inv ->
+ if (inv.method == method) {
+ null
+ } else {
+ inv.method.getAnnotation(NetworkCallback.FilteredCallback::class.java)?.methodId
+ }
+ }.toSet()
+ }
+
+ private fun getMockFor(method: Method, c: Class<*>): Any {
+ if (!c.isPrimitive && !Modifier.isFinal(c.modifiers)) {
+ return mock(c)
+ }
+ return when (c) {
+ NetworkCapabilities::class.java -> NetworkCapabilities()
+ LinkProperties::class.java -> LinkProperties()
+ LocalNetworkInfo::class.java -> LocalNetworkInfo(null)
+ Boolean::class.java -> false
+ Int::class.java -> 0
+ else -> fail("No mock set for parameter type $c used in $method")
+ }
+ }
+}
diff --git a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt b/tests/unit/java/android/net/NetworkStackBpfNetMapsTest.kt
similarity index 72%
rename from tests/unit/java/android/net/BpfNetMapsReaderTest.kt
rename to tests/unit/java/android/net/NetworkStackBpfNetMapsTest.kt
index 8919666..b5d78f3 100644
--- a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
+++ b/tests/unit/java/android/net/NetworkStackBpfNetMapsTest.kt
@@ -21,11 +21,13 @@
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
import android.os.Build.VERSION_CODES
+import android.os.Process.FIRST_APPLICATION_UID
import com.android.net.module.util.IBpfMap
import com.android.net.module.util.Struct.S32
import com.android.net.module.util.Struct.U32
@@ -42,15 +44,15 @@
import org.junit.Test
import org.junit.runner.RunWith
-private const val TEST_UID1 = 1234
+private const val TEST_UID1 = 11234
private const val TEST_UID2 = TEST_UID1 + 1
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)
-class BpfNetMapsReaderTest {
+@IgnoreUpTo(VERSION_CODES.TIRAMISU)
+class NetworkStackBpfNetMapsTest {
@Rule
@JvmField
val ignoreRule = DevSdkIgnoreRule()
@@ -58,14 +60,15 @@
private val testConfigurationMap: IBpfMap<S32, U32> = TestBpfMap()
private val testUidOwnerMap: IBpfMap<S32, UidOwnerValue> = TestBpfMap()
private val testDataSaverEnabledMap: IBpfMap<S32, U8> = TestBpfMap()
- private val bpfNetMapsReader = BpfNetMapsReader(
- TestDependencies(testConfigurationMap, testUidOwnerMap, testDataSaverEnabledMap))
+ private val bpfNetMapsReader = NetworkStackBpfNetMaps(
+ TestDependencies(testConfigurationMap, testUidOwnerMap, testDataSaverEnabledMap)
+ )
class TestDependencies(
private val configMap: IBpfMap<S32, U32>,
private val uidOwnerMap: IBpfMap<S32, UidOwnerValue>,
private val dataSaverEnabledMap: IBpfMap<S32, U8>
- ) : BpfNetMapsReader.Dependencies() {
+ ) : NetworkStackBpfNetMaps.Dependencies() {
override fun getConfigurationMap() = configMap
override fun getUidOwnerMap() = uidOwnerMap
override fun getDataSaverEnabledMap() = dataSaverEnabledMap
@@ -99,11 +102,20 @@
Modifier.isStatic(it.modifiers) && it.name.startsWith("FIREWALL_CHAIN_")
}
// Verify the size matches, this also verifies no common item in allow and deny chains.
- assertEquals(BpfNetMapsConstants.ALLOW_CHAINS.size +
- BpfNetMapsConstants.DENY_CHAINS.size, declaredChains.size)
+ assertEquals(
+ 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)))
+ assertTrue(
+ 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))
+ )
}
}
@@ -117,11 +129,17 @@
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(newConfig))
}
- fun isUidNetworkingBlocked(uid: Int, metered: Boolean = false, dataSaver: Boolean = false) =
- bpfNetMapsReader.isUidNetworkingBlocked(uid, metered, dataSaver)
+ private fun mockDataSaverEnabled(enabled: Boolean) {
+ val dataSaverValue = if (enabled) {DATA_SAVER_ENABLED} else {DATA_SAVER_DISABLED}
+ testDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, U8(dataSaverValue))
+ }
+
+ fun isUidNetworkingBlocked(uid: Int, metered: Boolean = false) =
+ bpfNetMapsReader.isUidNetworkingBlocked(uid, metered)
@Test
fun testIsUidNetworkingBlockedByFirewallChains_allowChain() {
+ mockDataSaverEnabled(enabled = false)
// With everything disabled by default, verify the return value is false.
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
assertFalse(isUidNetworkingBlocked(TEST_UID1))
@@ -141,6 +159,7 @@
@Test
fun testIsUidNetworkingBlockedByFirewallChains_denyChain() {
+ mockDataSaverEnabled(enabled = false)
// Enable standby chain but does not provide denied list. Verify the network is allowed
// for all uids.
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
@@ -162,47 +181,67 @@
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_POWERSAVE, true)
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true)
+ mockDataSaverEnabled(enabled = false)
assertTrue(isUidNetworkingBlocked(TEST_UID1))
}
@IgnoreUpTo(VERSION_CODES.S_V2)
@Test
fun testIsUidNetworkingBlockedByDataSaver() {
+ mockDataSaverEnabled(enabled = false)
// With everything disabled by default, verify the return value is false.
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
assertFalse(isUidNetworkingBlocked(TEST_UID1, metered = true))
// 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))
// Enable data saver, verify the network is blocked for uid1, uid2, but uid3 in happy box
// is not affected.
+ mockDataSaverEnabled(enabled = true)
testUidOwnerMap.updateEntry(S32(TEST_UID3), UidOwnerValue(NO_IIF, HAPPY_BOX_MATCH))
- assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
- assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
- assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true))
// Add uid1 to happy box as well, verify nothing is changed because penalty box has higher
// 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, dataSaver = true))
- assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
- assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
+ 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))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true))
// Enable doze mode, verify uid3 is blocked even if it is in happy box.
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true)
- assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
- assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
- assertTrue(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID3, metered = true))
// Disable doze mode and data saver, only uid1 which is in penalty box is blocked.
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, false)
+ mockDataSaverEnabled(enabled = false)
assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true))
@@ -214,6 +253,24 @@
}
@Test
+ fun testIsUidNetworkingBlocked_SystemUid() {
+ mockDataSaverEnabled(enabled = false)
+ testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
+ mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true)
+
+ 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_USER_MATCH))
+ assertEquals(
+ expectBlocked,
+ isUidNetworkingBlocked(uid, metered = true),
+ "isUidNetworkingBlocked returns unexpected value for uid = " + uid
+ )
+ }
+ }
+
+ @Test
fun testGetDataSaverEnabled() {
testDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, U8(DATA_SAVER_DISABLED))
assertFalse(bpfNetMapsReader.dataSaverEnabled)
diff --git a/tests/unit/java/android/net/VpnManagerTest.java b/tests/unit/java/android/net/VpnManagerTest.java
deleted file mode 100644
index 2ab4e45..0000000
--- a/tests/unit/java/android/net/VpnManagerTest.java
+++ /dev/null
@@ -1,148 +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;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.test.mock.MockContext;
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.net.VpnProfile;
-import com.android.internal.util.MessageUtils;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link VpnManager}. */
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class VpnManagerTest {
-
- private static final String PKG_NAME = "fooPackage";
-
- private static final String SESSION_NAME_STRING = "testSession";
- private static final String SERVER_ADDR_STRING = "1.2.3.4";
- private static final String IDENTITY_STRING = "Identity";
- private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
-
- private IVpnManager mMockService;
- private VpnManager mVpnManager;
- private final MockContext mMockContext =
- new MockContext() {
- @Override
- public String getOpPackageName() {
- return PKG_NAME;
- }
- };
-
- @Before
- public void setUp() throws Exception {
- assumeFalse("Skipping test because watches don't support VPN",
- InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH));
- mMockService = mock(IVpnManager.class);
- mVpnManager = new VpnManager(mMockContext, mMockService);
- }
-
- @Test
- public void testProvisionVpnProfilePreconsented() throws Exception {
- final PlatformVpnProfile profile = getPlatformVpnProfile();
- when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
- .thenReturn(true);
-
- // Expect there to be no intent returned, as consent has already been granted.
- assertNull(mVpnManager.provisionVpnProfile(profile));
- verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
- }
-
- @Test
- public void testProvisionVpnProfileNeedsConsent() throws Exception {
- final PlatformVpnProfile profile = getPlatformVpnProfile();
- when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
- .thenReturn(false);
-
- // Expect intent to be returned, as consent has not already been granted.
- final Intent intent = mVpnManager.provisionVpnProfile(profile);
- assertNotNull(intent);
-
- final ComponentName expectedComponentName =
- ComponentName.unflattenFromString(
- "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
- assertEquals(expectedComponentName, intent.getComponent());
- verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
- }
-
- @Test
- public void testDeleteProvisionedVpnProfile() throws Exception {
- mVpnManager.deleteProvisionedVpnProfile();
- verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
- }
-
- @Test
- public void testStartProvisionedVpnProfile() throws Exception {
- mVpnManager.startProvisionedVpnProfile();
- verify(mMockService).startVpnProfile(eq(PKG_NAME));
- }
-
- @Test
- public void testStopProvisionedVpnProfile() throws Exception {
- mVpnManager.stopProvisionedVpnProfile();
- verify(mMockService).stopVpnProfile(eq(PKG_NAME));
- }
-
- private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
- return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
- .setBypassable(true)
- .setMaxMtu(1300)
- .setMetered(true)
- .setAuthPsk(PSK_BYTES)
- .build();
- }
-
- @Test
- public void testVpnTypesEqual() throws Exception {
- SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames(
- new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" });
- SparseArray<String> nativeVpnType = MessageUtils.findMessageNames(
- new Class[] { NativeVpnType.class }, new String[]{ "" });
-
- // TYPE_VPN_NONE = -1 is only defined in VpnManager.
- assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size());
- for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) {
- assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i));
- }
- }
-}
diff --git a/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
index 332f2a3..c491f37 100644
--- a/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
+++ b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
@@ -54,17 +54,17 @@
assertEquals(beforeParcel.advertisingConfig, afterParcel.advertisingConfig)
}
-@Test
-fun testBuilder_setNullTtl_success() {
- val info = NsdServiceInfo().apply {
- serviceType = "_ipp._tcp"
- }
- val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
- .setTtl(null)
- .build()
+ @Test
+ fun testBuilder_setNullTtl_success() {
+ val info = NsdServiceInfo().apply {
+ serviceType = "_ipp._tcp"
+ }
+ val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+ .setTtl(null)
+ .build()
- assertNull(request.ttl)
-}
+ assertNull(request.ttl)
+ }
@Test
fun testBuilder_setPropertiesSuccess() {
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 76a649e..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;
@@ -240,7 +246,7 @@
AdvertisingRequest capturedRequest = getAdvertisingRequest(
req -> verify(mServiceConn).registerService(anyInt(), req.capture()));
- assertEquals(request, capturedRequest);
+ assertEquals(request.getTtl(), capturedRequest.getTtl());
}
private void doTestRegisterService() throws Exception {
@@ -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/android/net/nsd/NsdServiceInfoTest.kt b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.kt
new file mode 100644
index 0000000..8f86f06
--- /dev/null
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.kt
@@ -0,0 +1,51 @@
+/*
+ * 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 android.net.nsd
+
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit tests for {@link NsdServiceInfo}. */
+@SmallTest
+@ConnectivityModuleTest
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class NsdServiceInfoTest {
+ @Test
+ fun testToString_txtRecord() {
+ val info = NsdServiceInfo().apply {
+ this.setAttribute("abc", byteArrayOf(0xff.toByte(), 0xfe.toByte()))
+ this.setAttribute("def", null as String?)
+ this.setAttribute("ghi", "猫")
+ this.setAttribute("jkl", byteArrayOf(0, 0x21))
+ this.setAttribute("mno", "Hey Tom! It's you?.~{}")
+ }
+
+ val infoStr = info.toString()
+
+ assertTrue(
+ infoStr.contains("txtRecord: " +
+ "{abc=0xFFFE, def=(null), ghi=0xE78CAB, jkl=0x0021, mno=Hey Tom! It's you?.~{}}"),
+ infoStr)
+ }
+}
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
deleted file mode 100644
index acae7d2..0000000
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ /dev/null
@@ -1,323 +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 com.android.internal.net;
-
-import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
-import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
-
-import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
-import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.net.IpSecAlgorithm;
-import android.net.ipsec.ike.IkeTunnelConnectionParams;
-import android.os.Build;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/** Unit tests for {@link VpnProfile}. */
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class VpnProfileTest {
- private static final String DUMMY_PROFILE_KEY = "Test";
-
- private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
- private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
- private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
- private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
- private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27;
- private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28;
- private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29;
-
- @Test
- public void testDefaults() throws Exception {
- final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
-
- assertEquals(DUMMY_PROFILE_KEY, p.key);
- assertEquals("", p.name);
- assertEquals(VpnProfile.TYPE_PPTP, p.type);
- assertEquals("", p.server);
- assertEquals("", p.username);
- assertEquals("", p.password);
- assertEquals("", p.dnsServers);
- assertEquals("", p.searchDomains);
- assertEquals("", p.routes);
- assertTrue(p.mppe);
- assertEquals("", p.l2tpSecret);
- assertEquals("", p.ipsecIdentifier);
- assertEquals("", p.ipsecSecret);
- assertEquals("", p.ipsecUserCert);
- assertEquals("", p.ipsecCaCert);
- assertEquals("", p.ipsecServerCert);
- assertEquals(null, p.proxy);
- assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
- assertFalse(p.isBypassable);
- assertFalse(p.isMetered);
- assertEquals(1360, p.maxMtu);
- assertFalse(p.areAuthParamsInline);
- assertFalse(p.isRestrictedToTestNetworks);
- assertFalse(p.excludeLocalRoutes);
- assertFalse(p.requiresInternetValidation);
- assertFalse(p.automaticNattKeepaliveTimerEnabled);
- assertFalse(p.automaticIpVersionSelectionEnabled);
- }
-
- private VpnProfile getSampleIkev2Profile(String key) {
- final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
- false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
- null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */,
- true /* automaticIpVersionSelectionEnabled */);
-
- p.name = "foo";
- p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
- p.server = "bar";
- p.username = "baz";
- p.password = "qux";
- p.dnsServers = "8.8.8.8";
- p.searchDomains = "";
- p.routes = "0.0.0.0/0";
- p.mppe = false;
- p.l2tpSecret = "";
- p.ipsecIdentifier = "quux";
- p.ipsecSecret = "quuz";
- p.ipsecUserCert = "corge";
- p.ipsecCaCert = "grault";
- p.ipsecServerCert = "garply";
- p.proxy = null;
- p.setAllowedAlgorithms(
- Arrays.asList(
- IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
- IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
- IpSecAlgorithm.AUTH_HMAC_SHA512,
- IpSecAlgorithm.CRYPT_AES_CBC));
- p.isBypassable = true;
- p.isMetered = true;
- p.maxMtu = 1350;
- p.areAuthParamsInline = true;
-
- // Not saved, but also not compared.
- p.saveLogin = true;
-
- return p;
- }
-
- private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
- final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
- false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
- new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS),
- true /* mAutomaticNattKeepaliveTimerEnabled */,
- true /* automaticIpVersionSelectionEnabled */);
-
- p.name = "foo";
- p.server = "bar";
- p.dnsServers = "8.8.8.8";
- p.searchDomains = "";
- p.routes = "0.0.0.0/0";
- p.mppe = false;
- p.proxy = null;
- p.setAllowedAlgorithms(
- Arrays.asList(
- IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
- IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
- IpSecAlgorithm.AUTH_HMAC_SHA512,
- IpSecAlgorithm.CRYPT_AES_CBC));
- p.isBypassable = true;
- p.isMetered = true;
- p.maxMtu = 1350;
- p.areAuthParamsInline = true;
-
- // Not saved, but also not compared.
- p.saveLogin = true;
-
- return p;
- }
-
- @Test
- public void testEquals() {
- assertEquals(
- getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY));
-
- final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- modified.maxMtu--;
- assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified);
- }
-
- @Test
- public void testParcelUnparcel() {
- if (isAtLeastU()) {
- // automaticNattKeepaliveTimerEnabled, automaticIpVersionSelectionEnabled added in U.
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28);
- assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28);
- } else if (isAtLeastT()) {
- // excludeLocalRoutes, requiresPlatformValidation were added in T.
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 26);
- assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 26);
- } else {
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
- }
- }
-
- @Test
- public void testEncodeDecodeWithIkeTunConnParams() {
- final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
- assertEquals(profile, decoded);
- }
-
- @Test
- public void testEncodeDecode() {
- final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
- assertEquals(profile, decoded);
- }
-
- @Test
- public void testEncodeDecodeTooManyValues() {
- final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- final byte[] tooManyValues =
- (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes();
-
- assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
- }
-
- private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) {
- // Sort to ensure when we remove, we can do it from greatest first.
- Arrays.sort(missingIndices);
-
- final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode());
- final List<String> parts =
- new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER)));
-
- // Remove from back first to ensure indexing is consistent.
- for (int i = missingIndices.length - 1; i >= 0; i--) {
- parts.remove(missingIndices[i]);
- }
-
- return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0]));
- }
-
- @Test
- public void testEncodeDecodeInvalidNumberOfValues() {
- final String tooFewValues =
- getEncodedDecodedIkev2ProfileMissingValues(
- ENCODED_INDEX_AUTH_PARAMS_INLINE,
- ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
- ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
- ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
- ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
- ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
- ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED
- /* missingIndices */);
-
- assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
- }
-
- private String getEncodedDecodedIkev2ProfileWithtooFewValues() {
- return getEncodedDecodedIkev2ProfileMissingValues(
- ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
- ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
- ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
- ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
- ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
- ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */);
- }
-
- @Test
- public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without isRestrictedToTestNetworks defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.isRestrictedToTestNetworks);
- }
-
- @Test
- public void testEncodeDecodeMissingExcludeLocalRoutes() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without excludeLocalRoutes defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.excludeLocalRoutes);
- }
-
- @Test
- public void testEncodeDecodeMissingRequiresValidation() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without requiresValidation defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.requiresInternetValidation);
- }
-
- @Test
- public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.automaticNattKeepaliveTimerEnabled);
- }
-
- @Test
- public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without automaticIpVersionSelectionEnabled defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.automaticIpVersionSelectionEnabled);
- }
-
- @Test
- public void testEncodeDecodeLoginsNotSaved() {
- final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- profile.saveLogin = false;
-
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
- assertNotEquals(profile, decoded);
-
- // Add the username/password back, everything else must be equal.
- decoded.username = profile.username;
- decoded.password = profile.password;
- assertEquals(profile, decoded);
- }
-
- @Test
- public void testClone() {
- final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- final VpnProfile clone = profile.clone();
- assertEquals(profile, clone);
- assertNotSame(profile, clone);
- }
-}
diff --git a/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
index 53baee1..8a9286f 100644
--- a/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
@@ -1,5 +1,6 @@
package com.android.metrics
+import android.net.ConnectivityThread
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.CONNECTIVITY_MANAGED_CAPABILITIES
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
@@ -15,13 +16,20 @@
import android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
import android.net.NetworkScore
import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
import android.net.NetworkScore.POLICY_EXITING
import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
import android.os.Build
import android.os.Handler
+import android.os.Process
+import android.os.Process.SYSTEM_UID
import android.stats.connectivity.MeteredState
+import android.stats.connectivity.RequestType
+import android.stats.connectivity.RequestType.RT_APP
+import android.stats.connectivity.RequestType.RT_SYSTEM
+import android.stats.connectivity.RequestType.RT_SYSTEM_ON_BEHALF_OF_APP
import android.stats.connectivity.ValidatedState
import androidx.test.filters.SmallTest
import com.android.net.module.util.BitUtils
@@ -31,11 +39,13 @@
import com.android.server.connectivity.FullScore.POLICY_IS_UNMETERED
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
-import org.junit.Test
-import org.junit.runner.RunWith
+import com.android.testutils.TestableNetworkCallback
import java.util.concurrent.CompletableFuture
import kotlin.test.assertEquals
import kotlin.test.fail
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
private fun <T> Handler.onHandler(f: () -> T): T {
val future = CompletableFuture<T>()
@@ -80,7 +90,7 @@
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
class ConnectivitySampleMetricsTest : CSTest() {
@Test
- fun testSampleConnectivityState() {
+ fun testSampleConnectivityState_Network() {
val wifi1Caps = NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_WIFI)
.addCapability(NET_CAPABILITY_NOT_METERED)
@@ -179,4 +189,61 @@
"expected ${expectedWifi2Policies.toPolicyString()}, " +
"found ${foundWifi2.scorePolicies.toPolicyString()}")
}
+
+ private fun fileNetworkRequest(requestType: RequestType, requestCount: Int, uid: Int? = null) {
+ if (uid != null) {
+ deps.setCallingUid(uid)
+ }
+ try {
+ repeat(requestCount) {
+ when (requestType) {
+ RT_APP, RT_SYSTEM -> cm.requestNetwork(
+ NetworkRequest.Builder().build(),
+ TestableNetworkCallback()
+ )
+
+ RT_SYSTEM_ON_BEHALF_OF_APP -> cm.registerDefaultNetworkCallbackForUid(
+ Process.myUid(),
+ TestableNetworkCallback(),
+ Handler(ConnectivityThread.getInstanceLooper()))
+
+ else -> fail("invalid requestType: " + requestType)
+ }
+ }
+ } finally {
+ deps.unmockCallingUid()
+ }
+ }
+
+
+ @Test
+ fun testSampleConnectivityState_NetworkRequest() {
+ val requestCount = 5
+ fileNetworkRequest(RT_APP, requestCount);
+ fileNetworkRequest(RT_SYSTEM, requestCount, SYSTEM_UID);
+ fileNetworkRequest(RT_SYSTEM_ON_BEHALF_OF_APP, requestCount, SYSTEM_UID);
+
+ val stats = csHandler.onHandler { service.sampleConnectivityState() }
+
+ assertEquals(3, stats.networkRequestCount.requestCountForTypeList.size)
+ val appRequest = stats.networkRequestCount.requestCountForTypeList.find {
+ it.requestType == RT_APP
+ } ?: fail("Can't find RT_APP request")
+ val systemRequest = stats.networkRequestCount.requestCountForTypeList.find {
+ it.requestType == RT_SYSTEM
+ } ?: fail("Can't find RT_SYSTEM request")
+ val systemOnBehalfOfAppRequest = stats.networkRequestCount.requestCountForTypeList.find {
+ it.requestType == RT_SYSTEM_ON_BEHALF_OF_APP
+ } ?: fail("Can't find RT_SYSTEM_ON_BEHALF_OF_APP request")
+
+ // Verify request count is equal or larger than the number of request this test filed
+ // since ConnectivityService internally files network requests
+ assertTrue("Unexpected RT_APP count, expected >= $requestCount, " +
+ "found ${appRequest.requestCount}", appRequest.requestCount >= requestCount)
+ assertTrue("Unexpected RT_SYSTEM count, expected >= $requestCount, " +
+ "found ${systemRequest.requestCount}", systemRequest.requestCount >= requestCount)
+ assertTrue("Unexpected RT_SYSTEM_ON_BEHALF_OF_APP count, expected >= $requestCount, " +
+ "found ${systemOnBehalfOfAppRequest.requestCount}",
+ systemOnBehalfOfAppRequest.requestCount >= requestCount)
+ }
}
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/BpfLoaderRcUtilsTest.kt b/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
deleted file mode 100644
index 2cf6b17..0000000
--- a/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
+++ /dev/null
@@ -1,72 +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 com.android.server
-
-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.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@SmallTest
-@IgnoreUpTo(Build.VERSION_CODES.S)
-class BpfLoaderRcUtilsTest {
- @Test
- fun testLoadExistingBpfRcFile() {
-
- val inputString = """
- service a
- # test comment
- service bpfloader /system/bin/bpfloader
- capabilities CHOWN SYS_ADMIN NET_ADMIN
- group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
- user root
- rlimit memlock 1073741824 1073741824
- oneshot
- # comment 漢字
- reboot_on_failure reboot,bpfloader-failed
- updatable
-
- #test comment
- on b
- oneshot
- # test comment
- """.trimIndent()
- val expectedResult = listOf(
- "service bpfloader /system/bin/bpfloader",
- "capabilities CHOWN SYS_ADMIN NET_ADMIN",
- "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
- "user root",
- "rlimit memlock 1073741824 1073741824",
- "oneshot",
- "reboot_on_failure reboot,bpfloader-failed",
- "updatable"
- )
-
- assertEquals(expectedResult,
- BpfLoaderRcUtils.loadExistingBpfRcFile(inputString.byteInputStream()))
- }
-
- @Test
- fun testCheckBpfRcFile() {
- assertTrue(BpfLoaderRcUtils.checkBpfLoaderRc())
- }
-}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index ea905d5..859c54a 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;
@@ -74,7 +87,9 @@
import android.net.InetAddresses;
import android.net.UidOwnerValue;
import android.os.Build;
+import android.os.Process;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -334,146 +349,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 +533,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 +850,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 +972,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 +982,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 +1031,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 +1176,164 @@
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
+ );
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testIsUidNetworkingBlockedForCoreUids() throws Exception {
+ final long allowlistMatch = BACKGROUND_MATCH; // Enable any allowlist match.
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(allowlistMatch));
+
+ // Verify that a normal uid that is not on this chain is indeed blocked.
+ assertTrue(BpfNetMapsUtils.isUidNetworkingBlocked(TEST_UID, false, mConfigurationMap,
+ mUidOwnerMap, mDataSaverEnabledMap));
+
+ final int[] coreAids = new int[] {
+ Process.ROOT_UID,
+ Process.SYSTEM_UID,
+ Process.FIRST_APPLICATION_UID - 10,
+ Process.FIRST_APPLICATION_UID - 1,
+ };
+ // Core appIds are not on the chain but should still be allowed on any user.
+ for (int userId = 0; userId < 20; userId++) {
+ for (final int aid : coreAids) {
+ final int uid = UserHandle.getUid(userId, aid);
+ assertFalse(BpfNetMapsUtils.isUidNetworkingBlocked(uid, false, mConfigurationMap,
+ mUidOwnerMap, mDataSaverEnabledMap));
+ }
+ }
+ }
+
+ 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 c534025..be7f2a3 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,11 +164,9 @@
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;
-import static com.android.server.ConnectivityService.LOG_BPF_RC;
import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_OEM;
@@ -173,6 +177,9 @@
import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
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;
import static com.android.testutils.ConcurrentUtils.durationOf;
@@ -368,7 +375,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.security.Credentials;
import android.system.Os;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -389,10 +395,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.CollectionUtils;
@@ -424,7 +430,6 @@
import com.android.server.connectivity.SatelliteAccessController;
import com.android.server.connectivity.TcpKeepaliveController;
import com.android.server.connectivity.UidRangeUtils;
-import com.android.server.connectivity.VpnProfileStore;
import com.android.server.net.NetworkPinner;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -458,13 +463,13 @@
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;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -631,7 +636,6 @@
@Mock TelephonyManager mTelephonyManager;
@Mock EthernetManager mEthernetManager;
@Mock NetworkPolicyManager mNetworkPolicyManager;
- @Mock VpnProfileStore mVpnProfileStore;
@Mock SystemConfigManager mSystemConfigManager;
@Mock DevicePolicyManager mDevicePolicyManager;
@Mock Resources mResources;
@@ -802,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
@@ -1667,23 +1673,11 @@
waitForIdle();
}
- public void startLegacyVpnPrivileged(VpnProfile profile) {
- switch (profile.type) {
- case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
- case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
- case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
- case VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS:
- startPlatformVpn();
- break;
- case VpnProfile.TYPE_L2TP_IPSEC_PSK:
- case VpnProfile.TYPE_L2TP_IPSEC_RSA:
- case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
- case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
- case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
- startLegacyVpn();
- break;
- default:
- fail("Unknown VPN profile type");
+ public void startLegacyVpnPrivileged(boolean isIkev2Vpn) {
+ if (isIkev2Vpn) {
+ startPlatformVpn();
+ } else {
+ startLegacyVpn();
}
}
@@ -1736,6 +1730,8 @@
private void mockUidNetworkingBlocked() {
doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1))
).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
+ doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1))
+ ).when(mBpfNetMaps).isUidNetworkingBlocked(anyInt(), anyBoolean());
}
private boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) {
@@ -1751,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) {
@@ -1932,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.
@@ -1952,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.
@@ -2055,12 +2068,16 @@
};
}
+ private BiConsumer<Integer, Integer> mCarrierPrivilegesLostListener;
+
@Override
public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
@NonNull final Context context,
@NonNull final TelephonyManager tm,
final boolean requestRestrictedWifiEnabled,
- BiConsumer<Integer, Integer> listener) {
+ BiConsumer<Integer, Integer> listener,
+ @NonNull final Handler handler) {
+ mCarrierPrivilegesLostListener = listener;
return mDeps.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
}
@@ -2161,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);
}
@@ -2178,10 +2192,14 @@
switch (name) {
case ALLOW_SYSUI_CONNECTIVITY_REPORTS:
return true;
- case LOG_BPF_RC:
- return true;
case ALLOW_SATALLITE_NETWORK_FALLBACK:
return true;
+ case INGRESS_TO_VPN_ADDRESS_FILTERING:
+ return true;
+ case BACKGROUND_FIREWALL_CHAIN:
+ return true;
+ case DELAY_DESTROY_SOCKETS:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
@@ -2853,7 +2871,7 @@
};
final NetworkRequest request = mService.listenForNetwork(caps, messenger, binder,
NetworkCallback.FLAG_NONE, mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), ~0 /* declaredMethodsFlag */);
mService.releaseNetworkRequest(request);
deathRecipient.get().binderDied();
// Wait for the release message to be processed.
@@ -5389,7 +5407,7 @@
mService.requestNetwork(Process.INVALID_UID, networkCapabilities,
NetworkRequest.Type.REQUEST.ordinal(), null, 0, null,
ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE,
- mContext.getPackageName(), getAttributionTag());
+ mContext.getPackageName(), getAttributionTag(), ~0 /* declaredMethodsFlag */);
});
final NetworkRequest.Builder builder =
@@ -7522,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<>();
@@ -9862,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);
}
@@ -10213,24 +10253,6 @@
doAsUid(Process.SYSTEM_UID, () -> mCm.unregisterNetworkCallback(perUidCb));
}
- private VpnProfile setupLockdownVpn(int profileType) {
- final String profileName = "testVpnProfile";
- final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8);
- doReturn(profileTag).when(mVpnProfileStore).get(Credentials.LOCKDOWN_VPN);
-
- final VpnProfile profile = new VpnProfile(profileName);
- profile.name = "My VPN";
- profile.server = "192.0.2.1";
- profile.dnsServers = "8.8.8.8";
- profile.ipsecIdentifier = "My ipsecIdentifier";
- profile.ipsecSecret = "My PSK";
- profile.type = profileType;
- final byte[] encodedProfile = profile.encode();
- doReturn(encodedProfile).when(mVpnProfileStore).get(Credentials.VPN + profileName);
-
- return profile;
- }
-
private void establishLegacyLockdownVpn(Network underlying) throws Exception {
// The legacy lockdown VPN only supports userId 0, and must have an underlying network.
assertNotNull(underlying);
@@ -10242,7 +10264,7 @@
mMockVpn.connect(true);
}
- private void doTestLockdownVpn(VpnProfile profile, boolean expectSetVpnDefaultForUids)
+ private void doTestLockdownVpn(boolean isIkev2Vpn)
throws Exception {
mServiceContext.setPermission(
Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
@@ -10280,8 +10302,8 @@
b.expectBroadcast();
// Simulate LockdownVpnTracker attempting to start the VPN since it received the
// systemDefault callback.
- mMockVpn.startLegacyVpnPrivileged(profile);
- if (expectSetVpnDefaultForUids) {
+ mMockVpn.startLegacyVpnPrivileged(isIkev2Vpn);
+ if (isIkev2Vpn) {
// setVpnDefaultForUids() releases the original network request and creates a VPN
// request so LOST callback is received.
defaultCallback.expect(LOST, mCellAgent);
@@ -10305,7 +10327,7 @@
final NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
b2.expectBroadcast();
b3.expectBroadcast();
- if (expectSetVpnDefaultForUids) {
+ if (isIkev2Vpn) {
// Due to the VPN default request, getActiveNetworkInfo() gets the VPN network as the
// network satisfier which has TYPE_VPN.
assertActiveNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
@@ -10351,14 +10373,15 @@
// callback with different network.
final ExpectedBroadcast b6 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
mMockVpn.stopVpnRunnerPrivileged();
- mMockVpn.startLegacyVpnPrivileged(profile);
+
+ mMockVpn.startLegacyVpnPrivileged(isIkev2Vpn);
// VPN network is disconnected (to restart)
callback.expect(LOST, mMockVpn);
defaultCallback.expect(LOST, mMockVpn);
// The network preference is cleared when VPN is disconnected so it receives callbacks for
// the system-wide default.
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiAgent);
- if (expectSetVpnDefaultForUids) {
+ if (isIkev2Vpn) {
// setVpnDefaultForUids() releases the original network request and creates a VPN
// request so LOST callback is received.
defaultCallback.expect(LOST, mWiFiAgent);
@@ -10367,7 +10390,7 @@
b6.expectBroadcast();
// While the VPN is reconnecting on the new network, everything is blocked.
- if (expectSetVpnDefaultForUids) {
+ if (isIkev2Vpn) {
// Due to the VPN default request, getActiveNetworkInfo() gets the mNoServiceNetwork
// as the network satisfier.
assertNull(mCm.getActiveNetworkInfo());
@@ -10388,7 +10411,7 @@
systemDefaultCallback.assertNoCallback();
b7.expectBroadcast();
b8.expectBroadcast();
- if (expectSetVpnDefaultForUids) {
+ if (isIkev2Vpn) {
// Due to the VPN default request, getActiveNetworkInfo() gets the VPN network as the
// network satisfier which has TYPE_VPN.
assertActiveNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
@@ -10414,7 +10437,7 @@
defaultCallback.assertNoCallback();
systemDefaultCallback.assertNoCallback();
- if (expectSetVpnDefaultForUids) {
+ if (isIkev2Vpn) {
// Due to the VPN default request, getActiveNetworkInfo() gets the VPN network as the
// network satisfier which has TYPE_VPN.
assertActiveNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
@@ -10455,14 +10478,12 @@
@Test
public void testLockdownVpn_LegacyVpnRunner() throws Exception {
- final VpnProfile profile = setupLockdownVpn(VpnProfile.TYPE_IPSEC_XAUTH_PSK);
- doTestLockdownVpn(profile, false /* expectSetVpnDefaultForUids */);
+ doTestLockdownVpn(false /* isIkev2Vpn */);
}
@Test
public void testLockdownVpn_Ikev2VpnRunner() throws Exception {
- final VpnProfile profile = setupLockdownVpn(VpnProfile.TYPE_IKEV2_IPSEC_PSK);
- doTestLockdownVpn(profile, true /* expectSetVpnDefaultForUids */);
+ doTestLockdownVpn(true /* isIkev2Vpn */);
}
@Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -10518,24 +10539,33 @@
doTestSetUidFirewallRule(FIREWALL_CHAIN_POWERSAVE, FIREWALL_RULE_DENY);
doTestSetUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, FIREWALL_RULE_DENY);
doTestSetUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, FIREWALL_RULE_DENY);
- doTestSetUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, FIREWALL_RULE_DENY);
+ if (SdkLevel.isAtLeastV()) {
+ // FIREWALL_CHAIN_BACKGROUND is only available on V+.
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, FIREWALL_RULE_DENY);
+ }
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)
public void testSetFirewallChainEnabled() throws Exception {
- final List<Integer> firewallChains = Arrays.asList(
+ final List<Integer> firewallChains = new ArrayList<>(Arrays.asList(
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
FIREWALL_CHAIN_LOW_POWER_STANDBY,
- FIREWALL_CHAIN_BACKGROUND,
FIREWALL_CHAIN_OEM_DENY_1,
FIREWALL_CHAIN_OEM_DENY_2,
- FIREWALL_CHAIN_OEM_DENY_3);
+ FIREWALL_CHAIN_OEM_DENY_3));
+ if (SdkLevel.isAtLeastV()) {
+ // FIREWALL_CHAIN_BACKGROUND is only available on V+.
+ firewallChains.add(FIREWALL_CHAIN_BACKGROUND);
+ }
for (final int chain: firewallChains) {
mCm.setFirewallChainEnabled(chain, true /* enabled */);
verify(mBpfNetMaps).setChildChain(chain, true /* enable */);
@@ -10582,7 +10612,10 @@
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_POWERSAVE, allowlist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_RESTRICTED, allowlist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_LOW_POWER_STANDBY, allowlist);
- doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_BACKGROUND, allowlist);
+ if (SdkLevel.isAtLeastV()) {
+ // FIREWALL_CHAIN_BACKGROUND is only available on V+.
+ doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_BACKGROUND, allowlist);
+ }
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_STANDBY, denylist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_OEM_DENY_1, denylist);
@@ -10604,7 +10637,10 @@
doTestReplaceFirewallChain(FIREWALL_CHAIN_POWERSAVE);
doTestReplaceFirewallChain(FIREWALL_CHAIN_RESTRICTED);
doTestReplaceFirewallChain(FIREWALL_CHAIN_LOW_POWER_STANDBY);
- doTestReplaceFirewallChain(FIREWALL_CHAIN_BACKGROUND);
+ if (SdkLevel.isAtLeastV()) {
+ // FIREWALL_CHAIN_BACKGROUND is only available on V+.
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_BACKGROUND);
+ }
doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_1);
doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_2);
doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_3);
@@ -13619,7 +13655,8 @@
IllegalArgumentException.class,
() -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0,
null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE,
- mContext.getPackageName(), getAttributionTag())
+ mContext.getPackageName(), getAttributionTag(),
+ ~0 /* declaredMethodsFlag */)
);
}
}
@@ -17341,21 +17378,7 @@
}
@Test
- public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception {
- mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
- final NetworkCapabilities nc = new NetworkCapabilities();
- nc.setSubscriptionIds(Collections.singleton(Process.myUid()));
-
- final NetworkCapabilities result =
- mService.networkCapabilitiesRestrictedForCallerPermissions(
- nc, Process.myPid(), Process.myUid());
- assertTrue(result.getSubscriptionIds().isEmpty());
- }
-
- @Test
- public void testSubIdsExistWithNetworkFactoryPermission() throws Exception {
- mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
-
+ public void testSubIdsExist() throws Exception {
final Set<Integer> subIds = Collections.singleton(Process.myUid());
final NetworkCapabilities nc = new NetworkCapabilities();
nc.setSubscriptionIds(subIds);
@@ -17381,8 +17404,7 @@
}
@Test
- public void testNetworkRequestWithSubIdsWithNetworkFactoryPermission() throws Exception {
- mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ public void testNetworkRequestWithSubIds() throws Exception {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
final NetworkCallback networkCallback1 = new NetworkCallback();
@@ -17398,21 +17420,6 @@
}
@Test
- public void testNetworkRequestWithSubIdsWithoutNetworkFactoryPermission() throws Exception {
- mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(
- mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
-
- final Class<SecurityException> expected = SecurityException.class;
- assertThrows(
- expected, () -> mCm.requestNetwork(getRequestWithSubIds(), new NetworkCallback()));
- assertThrows(expected, () -> mCm.requestNetwork(getRequestWithSubIds(), pendingIntent));
- assertThrows(
- expected,
- () -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback()));
- }
-
- @Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testCarrierConfigAppSendNetworkRequestForRestrictedWifi() throws Exception {
mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
@@ -17479,7 +17486,10 @@
.isCarrierServiceUidForNetworkCapabilities(eq(Process.myUid()), any());
doReturn(TEST_SUBSCRIPTION_ID).when(mCarrierPrivilegeAuthenticator)
.getSubIdFromNetworkCapabilities(any());
- mService.onCarrierPrivilegesLost(lostPrivilegeUid, lostPrivilegeSubId);
+
+ visibleOnHandlerThread(mCsHandlerThread.getThreadHandler(), () -> {
+ mDeps.mCarrierPrivilegesLostListener.accept(lostPrivilegeUid, lostPrivilegeSubId);
+ });
waitForIdle();
if (expectCapChanged) {
@@ -17493,11 +17503,12 @@
}
mWiFiAgent.disconnect();
- waitForIdle();
if (expectUnavailable) {
+ testFactory.expectRequestRemove();
testFactory.assertRequestCountEquals(0);
} else {
+ testFactory.expectRequestAdd();
testFactory.assertRequestCountEquals(1);
}
@@ -17547,6 +17558,47 @@
false /* expectUnavailable */,
true /* expectCapChanged */);
}
+
+ @Test
+ public void testAllowedUidsExistWithoutNetworkFactoryPermission() throws Exception {
+ // Make sure NETWORK_FACTORY permission is not granted.
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .build(),
+ cb);
+
+ final ArraySet<Integer> uids = new ArraySet<>();
+ uids.add(200);
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setAllowedUids(uids)
+ .setOwnerUid(Process.myUid())
+ .setAdministratorUids(new int[] {Process.myUid()})
+ .build();
+ final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(TRANSPORT_TEST,
+ new LinkProperties(), nc);
+ agent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(agent);
+
+ uids.add(300);
+ uids.add(400);
+ nc.setAllowedUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ if (mDeps.isAtLeastT()) {
+ // AllowedUids is not cleared even without the NETWORK_FACTORY permission
+ // because the caller is the owner of the network.
+ cb.expectCaps(agent, c -> c.getAllowedUids().equals(uids));
+ } else {
+ cb.assertNoCallback();
+ }
+ }
+
@Test
public void testAllowedUids() throws Exception {
final int preferenceOrder =
@@ -19189,6 +19241,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/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
index 7bd2b56..ab81abc 100644
--- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -21,6 +21,7 @@
import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
import static com.android.server.connectivity.ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK;
+import static com.android.testutils.HandlerUtils.visibleOnHandlerThread;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -45,6 +46,7 @@
import android.net.NetworkCapabilities;
import android.net.TelephonyNetworkSpecifier;
import android.os.Build;
+import android.os.Handler;
import android.os.HandlerThread;
import android.telephony.TelephonyManager;
@@ -56,6 +58,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
import org.junit.After;
import org.junit.Rule;
@@ -85,6 +88,7 @@
private static final int SUBSCRIPTION_COUNT = 2;
private static final int TEST_SUBSCRIPTION_ID = 1;
+ private static final int TIMEOUT_MS = 1_000;
@NonNull private final Context mContext;
@NonNull private final TelephonyManager mTelephonyManager;
@@ -97,13 +101,16 @@
private final String mTestPkg = "com.android.server.connectivity.test";
private final BroadcastReceiver mMultiSimBroadcastReceiver;
@NonNull private final HandlerThread mHandlerThread;
+ @NonNull private final Handler mCsHandler;
+ @NonNull private final HandlerThread mCsHandlerThread;
public class TestCarrierPrivilegeAuthenticator extends CarrierPrivilegeAuthenticator {
TestCarrierPrivilegeAuthenticator(@NonNull final Context c,
@NonNull final Dependencies deps,
- @NonNull final TelephonyManager t) {
+ @NonNull final TelephonyManager t,
+ @NonNull final Handler handler) {
super(c, deps, t, mTelephonyManagerShim, true /* requestRestrictedWifiEnabled */,
- mListener);
+ mListener, handler);
}
@Override
protected int getSubId(int slotIndex) {
@@ -112,8 +119,11 @@
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
mHandlerThread.quit();
+ mHandlerThread.join();
+ mCsHandlerThread.quit();
+ mCsHandlerThread.join();
}
/** Parameters to test both using callbacks or the old broadcast */
@@ -141,8 +151,14 @@
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = mCarrierConfigPkgUid;
doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(mTestPkg), anyInt());
- mCarrierPrivilegeAuthenticator =
- new TestCarrierPrivilegeAuthenticator(mContext, deps, mTelephonyManager);
+ mCsHandlerThread = new HandlerThread(
+ CarrierPrivilegeAuthenticatorTest.class.getSimpleName() + "-CsHandlerThread");
+ mCsHandlerThread.start();
+ mCsHandler = new Handler(mCsHandlerThread.getLooper());
+ mCarrierPrivilegeAuthenticator = new TestCarrierPrivilegeAuthenticator(mContext, deps,
+ mTelephonyManager, mCsHandler);
+ mCarrierPrivilegeAuthenticator.start();
+ HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mContext).registerReceiver(receiverCaptor.capture(), argThat(filter ->
@@ -178,7 +194,9 @@
assertNotNull(initialListeners.get(1));
assertEquals(2, initialListeners.size());
- initialListeners.get(0).onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ visibleOnHandlerThread(mCsHandler, () -> {
+ initialListeners.get(0).onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ });
final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR)
@@ -201,10 +219,10 @@
doReturn(1).when(mTelephonyManager).getActiveModemCount();
- // This is a little bit cavalier in that the call to onReceive is not on the handler
- // thread that was specified in registerReceiver.
- // TODO : capture the handler and call this on it if this causes flakiness.
- mMultiSimBroadcastReceiver.onReceive(mContext, buildTestMultiSimConfigBroadcastIntent());
+ visibleOnHandlerThread(mCsHandler, () -> {
+ mMultiSimBroadcastReceiver.onReceive(mContext,
+ buildTestMultiSimConfigBroadcastIntent());
+ });
// Check all listeners have been removed
for (CarrierPrivilegesListenerShim listener : initialListeners.values()) {
verify(mTelephonyManagerShim).removeCarrierPrivilegesListener(eq(listener));
@@ -216,7 +234,9 @@
assertNotNull(newListeners.get(0));
assertEquals(1, newListeners.size());
- newListeners.get(0).onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ visibleOnHandlerThread(mCsHandler, () -> {
+ newListeners.get(0).onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ });
final TelephonyNetworkSpecifier specifier =
new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID);
@@ -235,12 +255,17 @@
public void testCarrierPrivilegesLostDueToCarrierServiceUpdate() throws Exception {
final CarrierPrivilegesListenerShim l = getCarrierPrivilegesListeners().get(0);
- l.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
- l.onCarrierServiceChanged(null, mCarrierConfigPkgUid + 1);
+ visibleOnHandlerThread(mCsHandler, () -> {
+ l.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ l.onCarrierServiceChanged(null, mCarrierConfigPkgUid + 1);
+ });
if (mUseCallbacks) {
verify(mListener).accept(eq(mCarrierConfigPkgUid), eq(TEST_SUBSCRIPTION_ID));
}
- l.onCarrierServiceChanged(null, mCarrierConfigPkgUid + 2);
+
+ visibleOnHandlerThread(mCsHandler, () -> {
+ l.onCarrierServiceChanged(null, mCarrierConfigPkgUid + 2);
+ });
if (mUseCallbacks) {
verify(mListener).accept(eq(mCarrierConfigPkgUid + 1), eq(TEST_SUBSCRIPTION_ID));
}
@@ -260,8 +285,10 @@
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = mCarrierConfigPkgUid + 1;
doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(mTestPkg), anyInt());
- listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {});
- listener.onCarrierServiceChanged(null, applicationInfo.uid);
+ visibleOnHandlerThread(mCsHandler, () -> {
+ listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[]{});
+ listener.onCarrierServiceChanged(null, applicationInfo.uid);
+ });
assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
mCarrierConfigPkgUid, nc));
@@ -272,7 +299,9 @@
@Test
public void testDefaultSubscription() throws Exception {
final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
- listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ visibleOnHandlerThread(mCsHandler, () -> {
+ listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ });
final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
ncBuilder.addTransportType(TRANSPORT_CELLULAR);
@@ -297,7 +326,9 @@
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testNetworkCapabilitiesContainOneSubId() throws Exception {
final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
- listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ visibleOnHandlerThread(mCsHandler, () -> {
+ listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ });
final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
ncBuilder.addTransportType(TRANSPORT_WIFI);
@@ -311,7 +342,9 @@
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testNetworkCapabilitiesContainTwoSubIds() throws Exception {
final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
- listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ visibleOnHandlerThread(mCsHandler, () -> {
+ listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ });
final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
ncBuilder.addTransportType(TRANSPORT_WIFI);
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 88044be..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)),
@@ -526,13 +527,13 @@
+ "v4: /192.0.0.46, v6: /2001:db8:0:b11::464, pfx96: /64:ff9b::, "
+ "pid: 10483, cookie: 27149", dumpStrings[0].trim());
assertEquals("Forwarding rules:", dumpStrings[1].trim());
- assertEquals("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif",
+ assertEquals("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif (packets bytes)",
dumpStrings[2].trim());
- assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001",
+ assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001 (0 0)",
dumpStrings[3].trim());
- assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif",
+ assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif (packets bytes)",
dumpStrings[4].trim());
- assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether",
+ assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether (0 0)",
dumpStrings[5].trim());
} else {
assertEquals(1, dumpStrings.length);
@@ -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/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 7121ed4..727db58 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -113,6 +113,7 @@
private static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
private static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
+ private static final NetworkCapabilities BT_CAPABILITIES = new NetworkCapabilities();
static {
CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
@@ -128,6 +129,9 @@
VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
VPN_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+
+ BT_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
+ BT_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
}
/**
@@ -159,7 +163,9 @@
@Mock NetworkAgentInfo mWifiNai;
@Mock NetworkAgentInfo mCellNai;
@Mock NetworkAgentInfo mVpnNai;
+ @Mock NetworkAgentInfo mBluetoothNai;
@Mock NetworkInfo mNetworkInfo;
+ @Mock NetworkInfo mEmptyNetworkInfo;
ArgumentCaptor<Notification> mCaptor;
NetworkNotificationManager mManager;
@@ -174,6 +180,8 @@
mCellNai.networkInfo = mNetworkInfo;
mVpnNai.networkCapabilities = VPN_CAPABILITIES;
mVpnNai.networkInfo = mNetworkInfo;
+ mBluetoothNai.networkCapabilities = BT_CAPABILITIES;
+ mBluetoothNai.networkInfo = mEmptyNetworkInfo;
mDisplayMetrics.density = 2.275f;
doReturn(true).when(mVpnNai).isVPN();
doReturn(mResources).when(mCtx).getResources();
@@ -542,10 +550,11 @@
R.string.wifi_no_internet_detailed);
}
- private void runTelephonySignInNotificationTest(String testTitle, String testContents) {
+ private void runSignInNotificationTest(NetworkAgentInfo nai, String testTitle,
+ String testContents) {
final int id = 101;
final String tag = NetworkNotificationManager.tagFor(id);
- mManager.showNotification(id, SIGN_IN, mCellNai, null, null, false);
+ mManager.showNotification(id, SIGN_IN, nai, null, null, false);
final ArgumentCaptor<Notification> noteCaptor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(eq(tag), eq(SIGN_IN.eventId), noteCaptor.capture());
@@ -565,7 +574,7 @@
doReturn(testContents).when(mResources).getString(
R.string.mobile_network_available_no_internet_detailed, TEST_OPERATOR_NAME);
- runTelephonySignInNotificationTest(testTitle, testContents);
+ runSignInNotificationTest(mCellNai, testTitle, testContents);
}
@Test
@@ -579,6 +588,21 @@
doReturn(testContents).when(mResources).getString(
R.string.mobile_network_available_no_internet_detailed_unknown_carrier);
- runTelephonySignInNotificationTest(testTitle, testContents);
+ runSignInNotificationTest(mCellNai, testTitle, testContents);
+ }
+
+ @Test
+ public void testBluetoothSignInNotification_EmptyNotificationContents() {
+ final String testTitle = "Test title";
+ final String testContents = "Test contents";
+ doReturn(testTitle).when(mResources).getString(
+ R.string.network_available_sign_in, 0);
+ doReturn(testContents).when(mResources).getString(
+ eq(R.string.network_available_sign_in_detailed), any());
+
+ runSignInNotificationTest(mBluetoothNai, testTitle, testContents);
+ // The details should be queried with an empty string argument. In practice the notification
+ // contents may just be an empty string, since the default translation just outputs the arg.
+ verify(mResources).getString(eq(R.string.network_available_sign_in_detailed), eq(""));
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
index 193078b..7885325 100644
--- a/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
@@ -18,17 +18,22 @@
import android.Manifest
import android.app.role.OnRoleHoldersChangedListener
import android.app.role.RoleManager
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
-import android.content.pm.UserInfo
import android.os.Build
import android.os.Handler
+import android.os.Looper
import android.os.UserHandle
+import android.os.UserManager
import android.util.ArraySet
-import com.android.server.makeMockUserManager
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import java.util.concurrent.Executor
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,30 +41,32 @@
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import java.util.concurrent.Executor
-import java.util.function.Consumer
-private const val USER = 0
-val USER_INFO = UserInfo(USER, "" /* name */, UserInfo.FLAG_PRIMARY)
-val USER_HANDLE = UserHandle(USER)
private const val PRIMARY_USER = 0
private const val SECONDARY_USER = 10
private val PRIMARY_USER_HANDLE = UserHandle.of(PRIMARY_USER)
private val SECONDARY_USER_HANDLE = UserHandle.of(SECONDARY_USER)
+
// sms app names
private const val SMS_APP1 = "sms_app_1"
private const val SMS_APP2 = "sms_app_2"
+
// sms app ids
private const val SMS_APP_ID1 = 100
private const val SMS_APP_ID2 = 101
+
// UID for app1 and app2 on primary user
// These app could become default sms app for user1
private val PRIMARY_USER_SMS_APP_UID1 = UserHandle.getUid(PRIMARY_USER, SMS_APP_ID1)
private val PRIMARY_USER_SMS_APP_UID2 = UserHandle.getUid(PRIMARY_USER, SMS_APP_ID2)
+
// UID for app1 and app2 on secondary user
// These app could become default sms app for user2
private val SECONDARY_USER_SMS_APP_UID1 = UserHandle.getUid(SECONDARY_USER, SMS_APP_ID1)
@@ -69,154 +76,259 @@
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
class SatelliteAccessControllerTest {
private val context = mock(Context::class.java)
- private val mPackageManager = mock(PackageManager::class.java)
- private val mHandler = mock(Handler::class.java)
- private val mRoleManager =
- mock(SatelliteAccessController.Dependencies::class.java)
+ private val primaryUserContext = mock(Context::class.java)
+ private val secondaryUserContext = mock(Context::class.java)
+ private val mPackageManagerPrimaryUser = mock(PackageManager::class.java)
+ private val mPackageManagerSecondaryUser = mock(PackageManager::class.java)
+ private val mDeps = mock(SatelliteAccessController.Dependencies::class.java)
private val mCallback = mock(Consumer::class.java) as Consumer<Set<Int>>
- private val mSatelliteAccessController =
- SatelliteAccessController(context, mRoleManager, mCallback, mHandler)
+ private val userManager = mock(UserManager::class.java)
+ private val mHandler = Handler(Looper.getMainLooper())
+ private var mSatelliteAccessController =
+ SatelliteAccessController(context, mDeps, mCallback, mHandler)
private lateinit var mRoleHolderChangedListener: OnRoleHoldersChangedListener
+ private lateinit var mUserRemovedReceiver: BroadcastReceiver
+
+ private fun <T> mockService(name: String, clazz: Class<T>, service: T) {
+ doReturn(name).`when`(context).getSystemServiceName(clazz)
+ doReturn(service).`when`(context).getSystemService(name)
+ if (context.getSystemService(clazz) == null) {
+ // Test is using mockito-extended
+ doReturn(service).`when`(context).getSystemService(clazz)
+ }
+ }
+
@Before
@Throws(PackageManager.NameNotFoundException::class)
fun setup() {
- makeMockUserManager(USER_INFO, USER_HANDLE)
- doReturn(context).`when`(context).createContextAsUser(any(), anyInt())
- doReturn(mPackageManager).`when`(context).packageManager
+ doReturn(emptyList<UserHandle>()).`when`(userManager).getUserHandles(true)
+ mockService(Context.USER_SERVICE, UserManager::class.java, userManager)
- doReturn(PackageManager.PERMISSION_GRANTED)
- .`when`(mPackageManager)
- .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, SMS_APP1)
- doReturn(PackageManager.PERMISSION_GRANTED)
- .`when`(mPackageManager)
- .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, SMS_APP2)
+ doReturn(primaryUserContext).`when`(context).createContextAsUser(PRIMARY_USER_HANDLE, 0)
+ doReturn(mPackageManagerPrimaryUser).`when`(primaryUserContext).packageManager
- // Initialise default message application primary user package1
+ doReturn(secondaryUserContext).`when`(context).createContextAsUser(SECONDARY_USER_HANDLE, 0)
+ doReturn(mPackageManagerSecondaryUser).`when`(secondaryUserContext).packageManager
+
+ for (app in listOf(SMS_APP1, SMS_APP2)) {
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .`when`(mPackageManagerPrimaryUser)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, app)
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .`when`(mPackageManagerSecondaryUser)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, app)
+ }
+
+ // Initialise message application primary user package1
val applicationInfo1 = ApplicationInfo()
applicationInfo1.uid = PRIMARY_USER_SMS_APP_UID1
doReturn(applicationInfo1)
- .`when`(mPackageManager)
+ .`when`(mPackageManagerPrimaryUser)
.getApplicationInfo(eq(SMS_APP1), anyInt())
- // Initialise default message application primary user package2
+ // Initialise message application primary user package2
val applicationInfo2 = ApplicationInfo()
applicationInfo2.uid = PRIMARY_USER_SMS_APP_UID2
doReturn(applicationInfo2)
- .`when`(mPackageManager)
+ .`when`(mPackageManagerPrimaryUser)
.getApplicationInfo(eq(SMS_APP2), anyInt())
- // Get registered listener using captor
- val listenerCaptor = ArgumentCaptor.forClass(
- OnRoleHoldersChangedListener::class.java
- )
- mSatelliteAccessController.start()
- verify(mRoleManager).addOnRoleHoldersChangedListenerAsUser(
- any(Executor::class.java), listenerCaptor.capture(), any(UserHandle::class.java))
- mRoleHolderChangedListener = listenerCaptor.value
+ // Initialise message application secondary user package1
+ val applicationInfo3 = ApplicationInfo()
+ applicationInfo3.uid = SECONDARY_USER_SMS_APP_UID1
+ doReturn(applicationInfo3)
+ .`when`(mPackageManagerSecondaryUser)
+ .getApplicationInfo(eq(SMS_APP1), anyInt())
+
+ // Initialise message application secondary user package2
+ val applicationInfo4 = ApplicationInfo()
+ applicationInfo4.uid = SECONDARY_USER_SMS_APP_UID2
+ doReturn(applicationInfo4)
+ .`when`(mPackageManagerSecondaryUser)
+ .getApplicationInfo(eq(SMS_APP2), anyInt())
}
@Test
fun test_onRoleHoldersChanged_SatelliteFallbackUid_Changed_SingleUser() {
- doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
- PRIMARY_USER_HANDLE)
+ startSatelliteAccessController()
+ doReturn(listOf<String>()).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE
+ )
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
verify(mCallback, never()).accept(any())
// check DEFAULT_MESSAGING_APP1 is available as satellite network fallback uid
doReturn(listOf(SMS_APP1))
- .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ .`when`(mDeps).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID1))
// check SMS_APP2 is available as satellite network Fallback uid
- doReturn(listOf(SMS_APP2)).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
- PRIMARY_USER_HANDLE)
+ doReturn(listOf(SMS_APP2)).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE
+ )
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2))
// check no uid is available as satellite network fallback uid
- doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
- PRIMARY_USER_HANDLE)
+ doReturn(listOf<String>()).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE
+ )
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
verify(mCallback).accept(ArraySet())
}
@Test
fun test_onRoleHoldersChanged_NoSatelliteCommunicationPermission() {
- doReturn(listOf<Any>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
- PRIMARY_USER_HANDLE)
+ startSatelliteAccessController()
+ doReturn(listOf<Any>()).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE
+ )
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
verify(mCallback, never()).accept(any())
// check DEFAULT_MESSAGING_APP1 is not available as satellite network fallback uid
// since satellite communication permission not available.
doReturn(PackageManager.PERMISSION_DENIED)
- .`when`(mPackageManager)
+ .`when`(mPackageManagerPrimaryUser)
.checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, SMS_APP1)
doReturn(listOf(SMS_APP1))
- .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ .`when`(mDeps).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
verify(mCallback, never()).accept(any())
}
@Test
fun test_onRoleHoldersChanged_RoleSms_NotAvailable() {
+ startSatelliteAccessController()
doReturn(listOf(SMS_APP1))
- .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
- mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_BROWSER,
- PRIMARY_USER_HANDLE)
+ .`when`(mDeps).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(
+ RoleManager.ROLE_BROWSER,
+ PRIMARY_USER_HANDLE
+ )
verify(mCallback, never()).accept(any())
}
@Test
fun test_onRoleHoldersChanged_SatelliteNetworkFallbackUid_Changed_multiUser() {
- doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
- PRIMARY_USER_HANDLE)
+ startSatelliteAccessController()
+ doReturn(listOf<String>()).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE
+ )
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
verify(mCallback, never()).accept(any())
// check SMS_APP1 is available as satellite network fallback uid at primary user
doReturn(listOf(SMS_APP1))
- .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ .`when`(mDeps).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID1))
// check SMS_APP2 is available as satellite network fallback uid at primary user
- doReturn(listOf(SMS_APP2)).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
- PRIMARY_USER_HANDLE)
+ doReturn(listOf(SMS_APP2)).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE
+ )
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2))
// check SMS_APP1 is available as satellite network fallback uid at secondary user
- val applicationInfo1 = ApplicationInfo()
- applicationInfo1.uid = SECONDARY_USER_SMS_APP_UID1
- doReturn(applicationInfo1).`when`(mPackageManager)
- .getApplicationInfo(eq(SMS_APP1), anyInt())
- doReturn(listOf(SMS_APP1)).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
- SECONDARY_USER_HANDLE)
+ doReturn(listOf(SMS_APP1)).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ SECONDARY_USER_HANDLE
+ )
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2, SECONDARY_USER_SMS_APP_UID1))
// check no uid is available as satellite network fallback uid at primary user
- doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
- PRIMARY_USER_HANDLE)
- mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS,
- PRIMARY_USER_HANDLE)
+ doReturn(listOf<String>()).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE
+ )
+ mRoleHolderChangedListener.onRoleHoldersChanged(
+ RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE
+ )
verify(mCallback).accept(setOf(SECONDARY_USER_SMS_APP_UID1))
// check SMS_APP2 is available as satellite network fallback uid at secondary user
- applicationInfo1.uid = SECONDARY_USER_SMS_APP_UID2
- doReturn(applicationInfo1).`when`(mPackageManager)
- .getApplicationInfo(eq(SMS_APP2), anyInt())
doReturn(listOf(SMS_APP2))
- .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+ .`when`(mDeps).getRoleHoldersAsUser(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
verify(mCallback).accept(setOf(SECONDARY_USER_SMS_APP_UID2))
// check no uid is available as satellite network fallback uid at secondary user
- doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
- SECONDARY_USER_HANDLE)
+ doReturn(listOf<String>()).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ SECONDARY_USER_HANDLE
+ )
mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
verify(mCallback).accept(ArraySet())
}
+
+ @Test
+ fun test_SatelliteFallbackUidCallback_OnUserRemoval() {
+ startSatelliteAccessController()
+ // check SMS_APP2 is available as satellite network fallback uid at primary user
+ doReturn(listOf(SMS_APP2)).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE
+ )
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2))
+
+ // check SMS_APP1 is available as satellite network fallback uid at secondary user
+ doReturn(listOf(SMS_APP1)).`when`(mDeps).getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS,
+ SECONDARY_USER_HANDLE
+ )
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+ verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2, SECONDARY_USER_SMS_APP_UID1))
+
+ val userRemovalIntent = Intent(Intent.ACTION_USER_REMOVED)
+ userRemovalIntent.putExtra(Intent.EXTRA_USER, SECONDARY_USER_HANDLE)
+ mUserRemovedReceiver.onReceive(context, userRemovalIntent)
+ verify(mCallback, times(2)).accept(setOf(PRIMARY_USER_SMS_APP_UID2))
+ }
+
+ @Test
+ fun testOnStartUpCallbackSatelliteFallbackUidWithExistingUsers() {
+ doReturn(
+ listOf(PRIMARY_USER_HANDLE)
+ ).`when`(userManager).getUserHandles(true)
+ doReturn(listOf(SMS_APP1))
+ .`when`(mDeps).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ // At start up, SatelliteAccessController must call CS callback with existing users'
+ // default messaging apps uids.
+ startSatelliteAccessController()
+ verify(mCallback, timeout(500)).accept(setOf(PRIMARY_USER_SMS_APP_UID1))
+ }
+
+ private fun startSatelliteAccessController() {
+ mSatelliteAccessController.start()
+ // Get registered listener using captor
+ val listenerCaptor = ArgumentCaptor.forClass(OnRoleHoldersChangedListener::class.java)
+ verify(mDeps).addOnRoleHoldersChangedListenerAsUser(
+ any(Executor::class.java),
+ listenerCaptor.capture(),
+ any(UserHandle::class.java)
+ )
+ mRoleHolderChangedListener = listenerCaptor.value
+
+ // Get registered receiver using captor
+ val userRemovedReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver::class.java)
+ verify(context).registerReceiver(
+ userRemovedReceiverCaptor.capture(),
+ any(IntentFilter::class.java),
+ isNull(),
+ any(Handler::class.java)
+ )
+ mUserRemovedReceiver = userRemovedReceiverCaptor.value
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index b8ebf0f..df48f6c 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -286,7 +286,6 @@
postSync { socketCb.onInterfaceDestroyed(TEST_SOCKETKEY_1, mockSocket1) }
verify(mockInterfaceAdvertiser1).destroyNow()
- postSync { intAdvCbCaptor.value.onDestroyed(mockSocket1) }
verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE2))
}
@@ -364,10 +363,10 @@
verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO))
verify(cb).onOffloadStop(eq(TEST_INTERFACE2), eq(OFFLOAD_SERVICEINFO))
- // Interface advertisers call onDestroyed after sending exit announcements
- postSync { intAdvCbCaptor1.value.onDestroyed(mockSocket1) }
+ // Interface advertisers call onAllServicesRemoved after sending exit announcements
+ postSync { intAdvCbCaptor1.value.onAllServicesRemoved(mockSocket1) }
verify(socketProvider, never()).unrequestSocket(any())
- postSync { intAdvCbCaptor2.value.onDestroyed(mockSocket2) }
+ postSync { intAdvCbCaptor2.value.onAllServicesRemoved(mockSocket2) }
verify(socketProvider).unrequestSocket(socketCb)
}
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/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index 28608bb..629ac67 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -18,7 +18,6 @@
import android.net.InetAddresses.parseNumericAddress
import android.net.LinkAddress
-import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.os.Build
import android.os.HandlerThread
@@ -55,6 +54,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.inOrder
private const val LOG_TAG = "testlogtag"
private const val TIMEOUT_MS = 10_000L
@@ -65,6 +65,7 @@
private const val TEST_SERVICE_ID_1 = 42
private const val TEST_SERVICE_ID_DUPLICATE = 43
+private const val TEST_SERVICE_ID_2 = 44
private val TEST_SERVICE_1 = NsdServiceInfo().apply {
serviceType = "_testservice._tcp"
serviceName = "MyTestService"
@@ -78,6 +79,13 @@
port = 12345
}
+private val TEST_SERVICE_1_CUSTOM_HOST = NsdServiceInfo().apply {
+ serviceType = "_testservice._tcp"
+ serviceName = "MyTestService"
+ hostname = "MyTestHost"
+ port = 12345
+}
+
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsInterfaceAdvertiserTest {
@@ -179,7 +187,94 @@
// Exit announcements finish: the advertiser has no left service and destroys itself
announceCb.onFinished(testExitInfo)
thread.waitForIdle(TIMEOUT_MS)
- verify(cb).onDestroyed(socket)
+ verify(cb).onAllServicesRemoved(socket)
+ }
+
+ @Test
+ fun testAddRemoveServiceWithCustomHost_restartProbingForProbingServices() {
+ val customHost1 = NsdServiceInfo().apply {
+ hostname = "MyTestHost"
+ hostAddresses = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"))
+ }
+ addServiceAndFinishProbing(TEST_SERVICE_ID_1, customHost1)
+ addServiceAndFinishProbing(TEST_SERVICE_ID_2, TEST_SERVICE_1_CUSTOM_HOST)
+ repository.setServiceProbing(TEST_SERVICE_ID_2)
+ val probingInfo = mock(ProbingInfo::class.java)
+ doReturn("MyTestHost")
+ .`when`(repository).getHostnameForServiceId(TEST_SERVICE_ID_1)
+ doReturn(TEST_SERVICE_ID_2).`when`(probingInfo).serviceId
+ doReturn(listOf(probingInfo))
+ .`when`(repository).restartProbingForHostname("MyTestHost")
+ val inOrder = inOrder(prober, announcer)
+
+ // Remove the custom host: the custom host's announcement is stopped and the probing
+ // services which use that hostname are re-announced.
+ advertiser.removeService(TEST_SERVICE_ID_1)
+
+ inOrder.verify(prober).stop(TEST_SERVICE_ID_1)
+ inOrder.verify(announcer).stop(TEST_SERVICE_ID_1)
+ inOrder.verify(prober).stop(TEST_SERVICE_ID_2)
+ inOrder.verify(prober).startProbing(probingInfo)
+ }
+
+ @Test
+ fun testAddRemoveServiceWithCustomHost_restartAnnouncingForProbedServices() {
+ val customHost1 = NsdServiceInfo().apply {
+ hostname = "MyTestHost"
+ hostAddresses = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"))
+ }
+ addServiceAndFinishProbing(TEST_SERVICE_ID_1, customHost1)
+ val announcementInfo =
+ addServiceAndFinishProbing(TEST_SERVICE_ID_2, TEST_SERVICE_1_CUSTOM_HOST)
+ doReturn("MyTestHost")
+ .`when`(repository).getHostnameForServiceId(TEST_SERVICE_ID_1)
+ doReturn(listOf(announcementInfo))
+ .`when`(repository).restartAnnouncingForHostname("MyTestHost")
+ val inOrder = inOrder(prober, announcer)
+
+ // Remove the custom host: the custom host's announcement is stopped and the probed services
+ // which use that hostname are re-announced.
+ advertiser.removeService(TEST_SERVICE_ID_1)
+
+ inOrder.verify(prober).stop(TEST_SERVICE_ID_1)
+ inOrder.verify(announcer).stop(TEST_SERVICE_ID_1)
+ inOrder.verify(announcer).stop(TEST_SERVICE_ID_2)
+ inOrder.verify(announcer).startSending(TEST_SERVICE_ID_2, announcementInfo, 0L /* initialDelayMs */)
+ }
+
+ @Test
+ fun testAddMoreAddressesForCustomHost_restartAnnouncingForProbedServices() {
+ val customHost = NsdServiceInfo().apply {
+ hostname = "MyTestHost"
+ hostAddresses = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"))
+ }
+ doReturn("MyTestHost")
+ .`when`(repository).getHostnameForServiceId(TEST_SERVICE_ID_1)
+ doReturn("MyTestHost")
+ .`when`(repository).getHostnameForServiceId(TEST_SERVICE_ID_2)
+ val announcementInfo1 =
+ addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1_CUSTOM_HOST)
+
+ val probingInfo2 = addServiceAndStartProbing(TEST_SERVICE_ID_2, customHost)
+ val announcementInfo2 = AnnouncementInfo(TEST_SERVICE_ID_2, emptyList(), emptyList())
+ doReturn(announcementInfo2).`when`(repository).onProbingSucceeded(probingInfo2)
+ doReturn(listOf(announcementInfo1, announcementInfo2))
+ .`when`(repository).restartAnnouncingForHostname("MyTestHost")
+ probeCb.onFinished(probingInfo2)
+
+ val inOrder = inOrder(prober, announcer)
+
+ inOrder.verify(announcer)
+ .startSending(TEST_SERVICE_ID_2, announcementInfo2, 0L /* initialDelayMs */)
+ inOrder.verify(announcer).stop(TEST_SERVICE_ID_1)
+ inOrder.verify(announcer)
+ .startSending(TEST_SERVICE_ID_1, announcementInfo1, 0L /* initialDelayMs */)
}
@Test
@@ -422,8 +517,8 @@
verify(prober, never()).startProbing(any())
}
- private fun addServiceAndFinishProbing(serviceId: Int, serviceInfo: NsdServiceInfo):
- AnnouncementInfo {
+ private fun addServiceAndStartProbing(serviceId: Int, serviceInfo: NsdServiceInfo):
+ ProbingInfo {
val testProbingInfo = mock(ProbingInfo::class.java)
doReturn(serviceId).`when`(testProbingInfo).serviceId
doReturn(testProbingInfo).`when`(repository).setServiceProbing(serviceId)
@@ -432,8 +527,15 @@
verify(repository).addService(serviceId, serviceInfo, null /* ttl */)
verify(prober).startProbing(testProbingInfo)
+ return testProbingInfo
+ }
+
+ private fun addServiceAndFinishProbing(serviceId: Int, serviceInfo: NsdServiceInfo):
+ AnnouncementInfo {
+ val testProbingInfo = addServiceAndStartProbing(serviceId, serviceInfo)
+
// Simulate probing success: continues to announcing
- val testAnnouncementInfo = mock(AnnouncementInfo::class.java)
+ val testAnnouncementInfo = AnnouncementInfo(serviceId, emptyList(), emptyList())
doReturn(testAnnouncementInfo).`when`(repository).onProbingSucceeded(testProbingInfo)
probeCb.onFinished(testProbingInfo)
return testAnnouncementInfo
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index 9474464..fb3d183 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
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.timeout;
@@ -47,6 +48,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -55,6 +57,7 @@
import java.net.DatagramPacket;
import java.net.NetworkInterface;
import java.net.SocketException;
+import java.util.ArrayList;
import java.util.List;
@RunWith(DevSdkIgnoreRunner.class)
@@ -154,7 +157,7 @@
verify(mSocketCreationCallback).onSocketCreated(tetherSocketKey2);
// Send packet to IPv4 with mSocketKey and verify sending has been called.
- mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, mSocketKey,
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet), mSocketKey,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket).send(ipv4Packet);
@@ -162,7 +165,7 @@
verify(tetherIfaceSock2, never()).send(any());
// Send packet to IPv4 with onlyUseIpv6OnIpv6OnlyNetworks = true, the packet will be sent.
- mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, mSocketKey,
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet), mSocketKey,
true /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket, times(2)).send(ipv4Packet);
@@ -170,7 +173,7 @@
verify(tetherIfaceSock2, never()).send(any());
// Send packet to IPv6 with tetherSocketKey1 and verify sending has been called.
- mSocketClient.sendPacketRequestingMulticastResponse(ipv6Packet, tetherSocketKey1,
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv6Packet), tetherSocketKey1,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket, never()).send(ipv6Packet);
@@ -180,7 +183,7 @@
// Send packet to IPv6 with onlyUseIpv6OnIpv6OnlyNetworks = true, the packet will not be
// sent. Therefore, the tetherIfaceSock1.send() and tetherIfaceSock2.send() are still be
// called once.
- mSocketClient.sendPacketRequestingMulticastResponse(ipv6Packet, tetherSocketKey1,
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv6Packet), tetherSocketKey1,
true /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket, never()).send(ipv6Packet);
@@ -266,7 +269,7 @@
verify(mSocketCreationCallback).onSocketCreated(socketKey3);
// Send IPv4 packet on the mSocketKey and verify sending has been called.
- mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, mSocketKey,
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet), mSocketKey,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket).send(ipv4Packet);
@@ -295,7 +298,7 @@
verify(socketCreationCb2).onSocketCreated(socketKey3);
// Send IPv4 packet on socket2 and verify sending to the socket2 only.
- mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, socketKey2,
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet), socketKey2,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
// ipv4Packet still sent only once on mSocket: times(1) matches the packet sent earlier on
@@ -309,7 +312,7 @@
verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback2);
// Send IPv4 packet again and verify it's still sent a second time
- mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, socketKey2,
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet), socketKey2,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(socket2, times(2)).send(ipv4Packet);
@@ -320,7 +323,7 @@
verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback);
// Send IPv4 packet and verify no more sending.
- mSocketClient.sendPacketRequestingMulticastResponse(ipv4Packet, mSocketKey,
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet), mSocketKey,
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket, times(1)).send(ipv4Packet);
@@ -407,4 +410,31 @@
verify(creationCallback3).onSocketDestroyed(mSocketKey);
verify(creationCallback3, never()).onSocketDestroyed(socketKey2);
}
+
+ @Test
+ public void testSendPacketWithMultipleDatagramPacket() throws IOException {
+ final SocketCallback callback = expectSocketCallback();
+ final List<DatagramPacket> packets = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ packets.add(new DatagramPacket(new byte[10 + i] /* buff */, 0 /* offset */,
+ 10 + i /* length */, MdnsConstants.IPV4_SOCKET_ADDR));
+ }
+ doReturn(true).when(mSocket).hasJoinedIpv4();
+ doReturn(true).when(mSocket).hasJoinedIpv6();
+ doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+
+ // Notify socket created
+ callback.onSocketCreated(mSocketKey, mSocket, List.of());
+ verify(mSocketCreationCallback).onSocketCreated(mSocketKey);
+
+ // Send packets to IPv4 with mSocketKey then verify sending has been called and the
+ // sequence is correct.
+ mSocketClient.sendPacketRequestingMulticastResponse(packets, mSocketKey,
+ false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ InOrder inOrder = inOrder(mSocket);
+ for (int i = 0; i < 10; i++) {
+ inOrder.verify(mSocket).send(packets.get(i));
+ }
+ }
}
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 8d1dff6..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,11 +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.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
@@ -38,6 +40,7 @@
import java.net.InetSocketAddress
import java.net.NetworkInterface
import java.util.Collections
+import java.time.Duration
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@@ -111,6 +114,28 @@
port = TEST_PORT
}
+private val TEST_SERVICE_CUSTOM_HOST_NO_ADDRESSES = NsdServiceInfo().apply {
+ hostname = "TestHost"
+ hostAddresses = listOf()
+ serviceType = "_testservice._tcp"
+ serviceName = "TestService"
+ 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 {
@@ -118,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()
}
@@ -146,7 +184,7 @@
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
assertEquals(0, repository.servicesCount)
assertEquals(-1,
- repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */))
+ repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, Duration.ofSeconds(50)))
assertEquals(1, repository.servicesCount)
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@@ -169,7 +207,7 @@
assertEquals(MdnsServiceRecord(expectedName,
0L /* receiptTimeMillis */,
false /* cacheFlush */,
- SHORT_TTL /* ttlMillis */,
+ 50_000L /* ttlMillis */,
0 /* servicePriority */, 0 /* serviceWeight */,
TEST_PORT, TEST_HOSTNAME), packet.authorityRecords[0])
@@ -559,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")
}
@@ -886,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(
@@ -989,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 */)
@@ -1103,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 */
@@ -1122,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 */
@@ -1149,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 */
@@ -1171,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 */)
@@ -1675,6 +2167,127 @@
assertEquals(0, reply.additionalAnswers.size)
assertEquals(knownAnswers, reply.knownAnswers)
}
+
+ @Test
+ fun testRestartProbingForHostname() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1,
+ setOf(TEST_SUBTYPE, TEST_SUBTYPE2))
+ repository.addService(TEST_SERVICE_CUSTOM_HOST_ID_1,
+ TEST_SERVICE_CUSTOM_HOST_NO_ADDRESSES, null)
+ repository.setServiceProbing(TEST_SERVICE_CUSTOM_HOST_ID_1)
+ repository.removeService(TEST_CUSTOM_HOST_ID_1)
+
+ val probingInfos = repository.restartProbingForHostname("TestHost")
+
+ assertEquals(1, probingInfos.size)
+ val probingInfo = probingInfos.get(0)
+ assertEquals(TEST_SERVICE_CUSTOM_HOST_ID_1, probingInfo.serviceId)
+ val packet = probingInfo.getPacket(0)
+ assertEquals(0, packet.transactionId)
+ assertEquals(MdnsConstants.FLAGS_QUERY, packet.flags)
+ assertEquals(0, packet.answers.size)
+ assertEquals(0, packet.additionalRecords.size)
+ assertEquals(1, packet.questions.size)
+ val serviceName = arrayOf("TestService", "_testservice", "_tcp", "local")
+ assertEquals(MdnsAnyRecord(serviceName, false /* unicast */), packet.questions[0])
+ assertThat(packet.authorityRecords).containsExactly(
+ MdnsServiceRecord(
+ serviceName,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ SHORT_TTL /* ttlMillis */,
+ 0 /* servicePriority */,
+ 0 /* serviceWeight */,
+ TEST_PORT,
+ TEST_CUSTOM_HOST_1_NAME))
+ }
+
+ @Test
+ fun testRestartAnnouncingForHostname() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1,
+ setOf(TEST_SUBTYPE, TEST_SUBTYPE2))
+ repository.addServiceAndFinishProbing(TEST_SERVICE_CUSTOM_HOST_ID_1,
+ TEST_SERVICE_CUSTOM_HOST_NO_ADDRESSES)
+ repository.removeService(TEST_CUSTOM_HOST_ID_1)
+
+ val announcementInfos = repository.restartAnnouncingForHostname("TestHost")
+
+ assertEquals(1, announcementInfos.size)
+ val announcementInfo = announcementInfos.get(0)
+ assertEquals(TEST_SERVICE_CUSTOM_HOST_ID_1, announcementInfo.serviceId)
+ val packet = announcementInfo.getPacket(0)
+ assertEquals(0, packet.transactionId)
+ assertEquals(0x8400 /* response, authoritative */, packet.flags)
+ assertEquals(0, packet.questions.size)
+ assertEquals(0, packet.authorityRecords.size)
+ val serviceName = arrayOf("TestService", "_testservice", "_tcp", "local")
+ val serviceType = arrayOf("_testservice", "_tcp", "local")
+ val v4AddrRev = getReverseDnsAddress(TEST_ADDRESSES[0].address)
+ val v6Addr1Rev = getReverseDnsAddress(TEST_ADDRESSES[1].address)
+ val v6Addr2Rev = getReverseDnsAddress(TEST_ADDRESSES[2].address)
+ assertThat(packet.answers).containsExactly(
+ MdnsPointerRecord(
+ serviceType,
+ 0L /* receiptTimeMillis */,
+ // Not a unique name owned by the announcer, so cacheFlush=false
+ false /* cacheFlush */,
+ 4500000L /* ttlMillis */,
+ serviceName),
+ MdnsServiceRecord(
+ serviceName,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ 0 /* servicePriority */,
+ 0 /* serviceWeight */,
+ TEST_PORT /* servicePort */,
+ TEST_CUSTOM_HOST_1_NAME),
+ MdnsTextRecord(
+ serviceName,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 4500000L /* ttlMillis */,
+ emptyList() /* entries */),
+ MdnsPointerRecord(
+ arrayOf("_services", "_dns-sd", "_udp", "local"),
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 4500000L /* ttlMillis */,
+ serviceType))
+ assertThat(packet.additionalRecords).containsExactly(
+ MdnsNsecRecord(v4AddrRev,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ v4AddrRev,
+ intArrayOf(TYPE_PTR)),
+ MdnsNsecRecord(TEST_HOSTNAME,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ TEST_HOSTNAME,
+ intArrayOf(TYPE_A, TYPE_AAAA)),
+ MdnsNsecRecord(v6Addr1Rev,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ v6Addr1Rev,
+ intArrayOf(TYPE_PTR)),
+ MdnsNsecRecord(v6Addr2Rev,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ v6Addr2Rev,
+ intArrayOf(TYPE_PTR)),
+ MdnsNsecRecord(serviceName,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 4500000L /* ttlMillis */,
+ serviceName,
+ intArrayOf(TYPE_TXT, TYPE_SRV)))
+ }
}
private fun MdnsRecordRepository.initWithService(
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/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
index 8740e80..4ce8ba6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
@@ -54,7 +54,8 @@
"192.168.1.1",
"2001::1",
List.of("vn=Google Inc.", "mn=Google Nest Hub Max"),
- /* textEntries= */ null);
+ /* textEntries= */ null,
+ INTERFACE_INDEX_UNSPECIFIED);
assertTrue(info.getAttributeByKey("vn").equals("Google Inc."));
assertTrue(info.getAttributeByKey("mn").equals("Google Nest Hub Max"));
@@ -73,7 +74,8 @@
"2001::1",
/* textStrings= */ null,
List.of(MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
- MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max")));
+ MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max")),
+ INTERFACE_INDEX_UNSPECIFIED);
assertTrue(info.getAttributeByKey("vn").equals("Google Inc."));
assertTrue(info.getAttributeByKey("mn").equals("Google Nest Hub Max"));
@@ -93,7 +95,8 @@
List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
List.of(
MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
- MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max")));
+ MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max")),
+ INTERFACE_INDEX_UNSPECIFIED);
assertEquals(Map.of("vn", "Google Inc.", "mn", "Google Nest Hub Max"),
info.getAttributes());
@@ -113,7 +116,8 @@
List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
List.of(MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max"),
- MdnsServiceInfo.TextEntry.fromString("mn=Google WiFi Router")));
+ MdnsServiceInfo.TextEntry.fromString("mn=Google WiFi Router")),
+ INTERFACE_INDEX_UNSPECIFIED);
assertEquals(Map.of("vn", "Google Inc.", "mn", "Google Nest Hub Max"),
info.getAttributes());
@@ -131,7 +135,8 @@
"192.168.1.1",
"2001::1",
List.of("KEY=Value"),
- /* textEntries= */ null);
+ /* textEntries= */ null,
+ INTERFACE_INDEX_UNSPECIFIED);
assertEquals("Value", info.getAttributeByKey("key"));
assertEquals("Value", info.getAttributeByKey("KEY"));
@@ -150,7 +155,9 @@
12345,
"192.168.1.1",
"2001::1",
- List.of());
+ List.of(),
+ /* textEntries= */ null,
+ INTERFACE_INDEX_UNSPECIFIED);
assertEquals(info.getInterfaceIndex(), INTERFACE_INDEX_UNSPECIFIED);
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 09236b1..44fa55c 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -38,6 +38,7 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
@@ -117,8 +118,6 @@
@Mock
private MdnsServiceBrowserListener mockListenerTwo;
@Mock
- private MdnsPacketWriter mockPacketWriter;
- @Mock
private MdnsMultinetworkSocketClient mockSocketClient;
@Mock
private Network mockNetwork;
@@ -145,6 +144,7 @@
private long latestDelayMs = 0;
private Message delayMessage = null;
private Handler realHandler = null;
+ private MdnsFeatureFlags featureFlags = MdnsFeatureFlags.newBuilder().build();
@Before
@SuppressWarnings("DoNotMock")
@@ -162,57 +162,59 @@
expectedIPv6Packets[i] = new DatagramPacket(buf, 0 /* offset */, 5 /* length */,
MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
}
- when(mockPacketWriter.getPacket(IPV4_ADDRESS))
- .thenReturn(expectedIPv4Packets[0])
- .thenReturn(expectedIPv4Packets[1])
- .thenReturn(expectedIPv4Packets[2])
- .thenReturn(expectedIPv4Packets[3])
- .thenReturn(expectedIPv4Packets[4])
- .thenReturn(expectedIPv4Packets[5])
- .thenReturn(expectedIPv4Packets[6])
- .thenReturn(expectedIPv4Packets[7])
- .thenReturn(expectedIPv4Packets[8])
- .thenReturn(expectedIPv4Packets[9])
- .thenReturn(expectedIPv4Packets[10])
- .thenReturn(expectedIPv4Packets[11])
- .thenReturn(expectedIPv4Packets[12])
- .thenReturn(expectedIPv4Packets[13])
- .thenReturn(expectedIPv4Packets[14])
- .thenReturn(expectedIPv4Packets[15])
- .thenReturn(expectedIPv4Packets[16])
- .thenReturn(expectedIPv4Packets[17])
- .thenReturn(expectedIPv4Packets[18])
- .thenReturn(expectedIPv4Packets[19])
- .thenReturn(expectedIPv4Packets[20])
- .thenReturn(expectedIPv4Packets[21])
- .thenReturn(expectedIPv4Packets[22])
- .thenReturn(expectedIPv4Packets[23]);
+ when(mockDeps.getDatagramPacketsFromMdnsPacket(
+ any(), any(MdnsPacket.class), eq(IPV4_ADDRESS), anyBoolean()))
+ .thenReturn(List.of(expectedIPv4Packets[0]))
+ .thenReturn(List.of(expectedIPv4Packets[1]))
+ .thenReturn(List.of(expectedIPv4Packets[2]))
+ .thenReturn(List.of(expectedIPv4Packets[3]))
+ .thenReturn(List.of(expectedIPv4Packets[4]))
+ .thenReturn(List.of(expectedIPv4Packets[5]))
+ .thenReturn(List.of(expectedIPv4Packets[6]))
+ .thenReturn(List.of(expectedIPv4Packets[7]))
+ .thenReturn(List.of(expectedIPv4Packets[8]))
+ .thenReturn(List.of(expectedIPv4Packets[9]))
+ .thenReturn(List.of(expectedIPv4Packets[10]))
+ .thenReturn(List.of(expectedIPv4Packets[11]))
+ .thenReturn(List.of(expectedIPv4Packets[12]))
+ .thenReturn(List.of(expectedIPv4Packets[13]))
+ .thenReturn(List.of(expectedIPv4Packets[14]))
+ .thenReturn(List.of(expectedIPv4Packets[15]))
+ .thenReturn(List.of(expectedIPv4Packets[16]))
+ .thenReturn(List.of(expectedIPv4Packets[17]))
+ .thenReturn(List.of(expectedIPv4Packets[18]))
+ .thenReturn(List.of(expectedIPv4Packets[19]))
+ .thenReturn(List.of(expectedIPv4Packets[20]))
+ .thenReturn(List.of(expectedIPv4Packets[21]))
+ .thenReturn(List.of(expectedIPv4Packets[22]))
+ .thenReturn(List.of(expectedIPv4Packets[23]));
- when(mockPacketWriter.getPacket(IPV6_ADDRESS))
- .thenReturn(expectedIPv6Packets[0])
- .thenReturn(expectedIPv6Packets[1])
- .thenReturn(expectedIPv6Packets[2])
- .thenReturn(expectedIPv6Packets[3])
- .thenReturn(expectedIPv6Packets[4])
- .thenReturn(expectedIPv6Packets[5])
- .thenReturn(expectedIPv6Packets[6])
- .thenReturn(expectedIPv6Packets[7])
- .thenReturn(expectedIPv6Packets[8])
- .thenReturn(expectedIPv6Packets[9])
- .thenReturn(expectedIPv6Packets[10])
- .thenReturn(expectedIPv6Packets[11])
- .thenReturn(expectedIPv6Packets[12])
- .thenReturn(expectedIPv6Packets[13])
- .thenReturn(expectedIPv6Packets[14])
- .thenReturn(expectedIPv6Packets[15])
- .thenReturn(expectedIPv6Packets[16])
- .thenReturn(expectedIPv6Packets[17])
- .thenReturn(expectedIPv6Packets[18])
- .thenReturn(expectedIPv6Packets[19])
- .thenReturn(expectedIPv6Packets[20])
- .thenReturn(expectedIPv6Packets[21])
- .thenReturn(expectedIPv6Packets[22])
- .thenReturn(expectedIPv6Packets[23]);
+ when(mockDeps.getDatagramPacketsFromMdnsPacket(
+ any(), any(MdnsPacket.class), eq(IPV6_ADDRESS), anyBoolean()))
+ .thenReturn(List.of(expectedIPv6Packets[0]))
+ .thenReturn(List.of(expectedIPv6Packets[1]))
+ .thenReturn(List.of(expectedIPv6Packets[2]))
+ .thenReturn(List.of(expectedIPv6Packets[3]))
+ .thenReturn(List.of(expectedIPv6Packets[4]))
+ .thenReturn(List.of(expectedIPv6Packets[5]))
+ .thenReturn(List.of(expectedIPv6Packets[6]))
+ .thenReturn(List.of(expectedIPv6Packets[7]))
+ .thenReturn(List.of(expectedIPv6Packets[8]))
+ .thenReturn(List.of(expectedIPv6Packets[9]))
+ .thenReturn(List.of(expectedIPv6Packets[10]))
+ .thenReturn(List.of(expectedIPv6Packets[11]))
+ .thenReturn(List.of(expectedIPv6Packets[12]))
+ .thenReturn(List.of(expectedIPv6Packets[13]))
+ .thenReturn(List.of(expectedIPv6Packets[14]))
+ .thenReturn(List.of(expectedIPv6Packets[15]))
+ .thenReturn(List.of(expectedIPv6Packets[16]))
+ .thenReturn(List.of(expectedIPv6Packets[17]))
+ .thenReturn(List.of(expectedIPv6Packets[18]))
+ .thenReturn(List.of(expectedIPv6Packets[19]))
+ .thenReturn(List.of(expectedIPv6Packets[20]))
+ .thenReturn(List.of(expectedIPv6Packets[21]))
+ .thenReturn(List.of(expectedIPv6Packets[22]))
+ .thenReturn(List.of(expectedIPv6Packets[23]));
thread = new HandlerThread("MdnsServiceTypeClientTests");
thread.start();
@@ -242,22 +244,13 @@
return true;
}).when(mockDeps).sendMessage(any(Handler.class), any(Message.class));
- client = makeMdnsServiceTypeClient(mockPacketWriter);
+ client = makeMdnsServiceTypeClient();
}
- private MdnsServiceTypeClient makeMdnsServiceTypeClient(
- @Nullable MdnsPacketWriter packetWriter) {
+ private MdnsServiceTypeClient makeMdnsServiceTypeClient() {
return new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache) {
- @Override
- MdnsPacketWriter createMdnsPacketWriter() {
- if (packetWriter == null) {
- return super.createMdnsPacketWriter();
- }
- return packetWriter;
- }
- };
+ serviceCache, featureFlags);
}
@After
@@ -697,27 +690,27 @@
@Test
public void testCombinedSubtypesQueriedWithMultipleListeners() throws Exception {
- client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
final MdnsSearchOptions searchOptions1 = MdnsSearchOptions.newBuilder()
.addSubtype("subtype1").build();
final MdnsSearchOptions searchOptions2 = MdnsSearchOptions.newBuilder()
.addSubtype("subtype2").build();
+ doCallRealMethod().when(mockDeps).getDatagramPacketsFromMdnsPacket(
+ any(), any(MdnsPacket.class), any(InetSocketAddress.class), anyBoolean());
startSendAndReceive(mockListenerOne, searchOptions1);
- currentThreadExecutor.getAndClearSubmittedRunnable().run();
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
InOrder inOrder = inOrder(mockListenerOne, mockSocketClient, mockDeps);
// Verify the query asks for subtype1
- final ArgumentCaptor<DatagramPacket> subtype1QueryCaptor =
- ArgumentCaptor.forClass(DatagramPacket.class);
- currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ final ArgumentCaptor<List<DatagramPacket>> subtype1QueryCaptor =
+ ArgumentCaptor.forClass(List.class);
// Send twice for IPv4 and IPv6
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
subtype1QueryCaptor.capture(),
eq(socketKey), eq(false));
final MdnsPacket subtype1Query = MdnsPacket.parse(
- new MdnsPacketReader(subtype1QueryCaptor.getValue()));
+ new MdnsPacketReader(subtype1QueryCaptor.getValue().get(0)));
assertEquals(2, subtype1Query.questions.size());
assertTrue(hasQuestion(subtype1Query, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
@@ -729,8 +722,8 @@
inOrder.verify(mockDeps).removeMessages(any(), eq(EVENT_START_QUERYTASK));
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
- final ArgumentCaptor<DatagramPacket> combinedSubtypesQueryCaptor =
- ArgumentCaptor.forClass(DatagramPacket.class);
+ final ArgumentCaptor<List<DatagramPacket>> combinedSubtypesQueryCaptor =
+ ArgumentCaptor.forClass(List.class);
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
combinedSubtypesQueryCaptor.capture(),
eq(socketKey), eq(false));
@@ -738,7 +731,7 @@
inOrder.verify(mockDeps).sendMessageDelayed(any(), any(), anyLong());
final MdnsPacket combinedSubtypesQuery = MdnsPacket.parse(
- new MdnsPacketReader(combinedSubtypesQueryCaptor.getValue()));
+ new MdnsPacketReader(combinedSubtypesQueryCaptor.getValue().get(0)));
assertEquals(3, combinedSubtypesQuery.questions.size());
assertTrue(hasQuestion(combinedSubtypesQuery, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
@@ -754,15 +747,15 @@
dispatchMessage();
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
- final ArgumentCaptor<DatagramPacket> subtype2QueryCaptor =
- ArgumentCaptor.forClass(DatagramPacket.class);
+ final ArgumentCaptor<List<DatagramPacket>> subtype2QueryCaptor =
+ ArgumentCaptor.forClass(List.class);
// Send twice for IPv4 and IPv6
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
subtype2QueryCaptor.capture(),
eq(socketKey), eq(false));
final MdnsPacket subtype2Query = MdnsPacket.parse(
- new MdnsPacketReader(subtype2QueryCaptor.getValue()));
+ new MdnsPacketReader(subtype2QueryCaptor.getValue().get(0)));
assertEquals(2, subtype2Query.questions.size());
assertTrue(hasQuestion(subtype2Query, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
@@ -1022,7 +1015,6 @@
public void processResponse_searchOptionsEnableServiceRemoval_shouldRemove()
throws Exception {
final String serviceInstanceName = "service-instance-1";
- client = makeMdnsServiceTypeClient(mockPacketWriter);
MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
.setRemoveExpiredService(true)
.setNumOfQueriesBeforeBackoff(Integer.MAX_VALUE)
@@ -1060,7 +1052,6 @@
public void processResponse_searchOptionsNotEnableServiceRemoval_shouldNotRemove()
throws Exception {
final String serviceInstanceName = "service-instance-1";
- client = makeMdnsServiceTypeClient(mockPacketWriter);
startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
@@ -1086,7 +1077,6 @@
throws Exception {
//MdnsConfigsFlagsImpl.removeServiceAfterTtlExpires.override(true);
final String serviceInstanceName = "service-instance-1";
- client = makeMdnsServiceTypeClient(mockPacketWriter);
startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
@@ -1201,8 +1191,6 @@
@Test
public void testProcessResponse_Resolve() throws Exception {
- client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
-
final String instanceName = "service-instance";
final String[] hostname = new String[] { "testhost "};
final String ipV4Address = "192.0.2.0";
@@ -1213,14 +1201,17 @@
final MdnsSearchOptions resolveOptions2 = MdnsSearchOptions.newBuilder()
.setResolveInstanceName(instanceName).build();
+ doCallRealMethod().when(mockDeps).getDatagramPacketsFromMdnsPacket(
+ any(), any(MdnsPacket.class), any(InetSocketAddress.class), anyBoolean());
+
startSendAndReceive(mockListenerOne, resolveOptions1);
startSendAndReceive(mockListenerTwo, resolveOptions2);
// No need to verify order for both listeners; and order is not guaranteed between them
InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
// Verify a query for SRV/TXT was sent, but no PTR query
- final ArgumentCaptor<DatagramPacket> srvTxtQueryCaptor =
- ArgumentCaptor.forClass(DatagramPacket.class);
+ final ArgumentCaptor<List<DatagramPacket>> srvTxtQueryCaptor =
+ ArgumentCaptor.forClass(List.class);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
// Send twice for IPv4 and IPv6
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
@@ -1232,7 +1223,7 @@
verify(mockListenerTwo).onDiscoveryQuerySent(any(), anyInt());
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
- new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
+ new MdnsPacketReader(srvTxtQueryCaptor.getValue().get(0)));
final String[] serviceName = getTestServiceName(instanceName);
assertEquals(1, srvTxtQueryPacket.questions.size());
@@ -1264,8 +1255,8 @@
// Expect a query for A/AAAA
dispatchMessage();
- final ArgumentCaptor<DatagramPacket> addressQueryCaptor =
- ArgumentCaptor.forClass(DatagramPacket.class);
+ final ArgumentCaptor<List<DatagramPacket>> addressQueryCaptor =
+ ArgumentCaptor.forClass(List.class);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
addressQueryCaptor.capture(),
@@ -1275,7 +1266,7 @@
verify(mockListenerTwo, times(2)).onDiscoveryQuerySent(any(), anyInt());
final MdnsPacket addressQueryPacket = MdnsPacket.parse(
- new MdnsPacketReader(addressQueryCaptor.getValue()));
+ new MdnsPacketReader(addressQueryCaptor.getValue().get(0)));
assertEquals(2, addressQueryPacket.questions.size());
assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_A, hostname));
assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_AAAA, hostname));
@@ -1317,8 +1308,6 @@
@Test
public void testRenewTxtSrvInResolve() throws Exception {
- client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
-
final String instanceName = "service-instance";
final String[] hostname = new String[] { "testhost "};
final String ipV4Address = "192.0.2.0";
@@ -1327,12 +1316,15 @@
final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
.setResolveInstanceName(instanceName).build();
+ doCallRealMethod().when(mockDeps).getDatagramPacketsFromMdnsPacket(
+ any(), any(MdnsPacket.class), any(InetSocketAddress.class), anyBoolean());
+
startSendAndReceive(mockListenerOne, resolveOptions);
InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
// Get the query for SRV/TXT
- final ArgumentCaptor<DatagramPacket> srvTxtQueryCaptor =
- ArgumentCaptor.forClass(DatagramPacket.class);
+ final ArgumentCaptor<List<DatagramPacket>> srvTxtQueryCaptor =
+ ArgumentCaptor.forClass(List.class);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
// Send twice for IPv4 and IPv6
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
@@ -1342,7 +1334,7 @@
assertNotNull(delayMessage);
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
- new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
+ new MdnsPacketReader(srvTxtQueryCaptor.getValue().get(0)));
final String[] serviceName = getTestServiceName(instanceName);
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_ANY, serviceName));
@@ -1386,8 +1378,8 @@
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
// Expect a renewal query
- final ArgumentCaptor<DatagramPacket> renewalQueryCaptor =
- ArgumentCaptor.forClass(DatagramPacket.class);
+ final ArgumentCaptor<List<DatagramPacket>> renewalQueryCaptor =
+ ArgumentCaptor.forClass(List.class);
// Second and later sends are sent as "expect multicast response" queries
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
renewalQueryCaptor.capture(),
@@ -1396,7 +1388,7 @@
assertNotNull(delayMessage);
inOrder.verify(mockListenerOne).onDiscoveryQuerySent(any(), anyInt());
final MdnsPacket renewalPacket = MdnsPacket.parse(
- new MdnsPacketReader(renewalQueryCaptor.getValue()));
+ new MdnsPacketReader(renewalQueryCaptor.getValue().get(0)));
assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_ANY, serviceName));
inOrder.verifyNoMoreInteractions();
@@ -1431,8 +1423,6 @@
@Test
public void testProcessResponse_ResolveExcludesOtherServices() {
- client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
-
final String requestedInstance = "instance1";
final String otherInstance = "instance2";
final String ipV4Address = "192.0.2.0";
@@ -1499,8 +1489,6 @@
@Test
public void testProcessResponse_SubtypeDiscoveryLimitedToSubtype() {
- client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
-
final String matchingInstance = "instance1";
final String subtype = "_subtype";
final String otherInstance = "instance2";
@@ -1587,8 +1575,6 @@
@Test
public void testProcessResponse_SubtypeChange() {
- client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
-
final String matchingInstance = "instance1";
final String subtype = "_subtype";
final String ipV4Address = "192.0.2.0";
@@ -1670,8 +1656,6 @@
@Test
public void testNotifySocketDestroyed() throws Exception {
- client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
-
final String requestedInstance = "instance1";
final String otherInstance = "instance2";
final String ipV4Address = "192.0.2.0";
@@ -1946,6 +1930,138 @@
16 /* scheduledCount */);
}
+ @Test
+ public void testSendQueryWithKnownAnswers() throws Exception {
+ client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache,
+ MdnsFeatureFlags.newBuilder().setIsQueryWithKnownAnswerEnabled(true).build());
+
+ doCallRealMethod().when(mockDeps).getDatagramPacketsFromMdnsPacket(
+ any(), any(MdnsPacket.class), any(InetSocketAddress.class), anyBoolean());
+
+ startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+ InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
+
+ final ArgumentCaptor<List<DatagramPacket>> queryCaptor =
+ ArgumentCaptor.forClass(List.class);
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ // Send twice for IPv4 and IPv6
+ inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
+ queryCaptor.capture(), eq(socketKey), eq(false));
+ verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
+ assertNotNull(delayMessage);
+
+ final MdnsPacket queryPacket = MdnsPacket.parse(
+ new MdnsPacketReader(queryCaptor.getValue().get(0)));
+ assertTrue(hasQuestion(queryPacket, MdnsRecord.TYPE_PTR));
+
+ // Process a response
+ final String serviceName = "service-instance";
+ final String ipV4Address = "192.0.2.0";
+ final String[] subtypeLabels = Stream.concat(Stream.of("_subtype", "_sub"),
+ Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
+ final MdnsPacket packetWithoutSubtype = createResponse(
+ serviceName, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+ Collections.emptyMap() /* textAttributes */, TEST_TTL);
+ final MdnsPointerRecord originalPtr = (MdnsPointerRecord) CollectionUtils.findFirst(
+ packetWithoutSubtype.answers, r -> r instanceof MdnsPointerRecord);
+
+ // Add a subtype PTR record
+ final ArrayList<MdnsRecord> newAnswers = new ArrayList<>(packetWithoutSubtype.answers);
+ newAnswers.add(new MdnsPointerRecord(subtypeLabels, originalPtr.getReceiptTime(),
+ originalPtr.getCacheFlush(), originalPtr.getTtl(), originalPtr.getPointer()));
+ final MdnsPacket packetWithSubtype = new MdnsPacket(
+ packetWithoutSubtype.flags,
+ packetWithoutSubtype.questions,
+ newAnswers,
+ packetWithoutSubtype.authorityRecords,
+ packetWithoutSubtype.additionalRecords);
+ processResponse(packetWithSubtype, socketKey);
+
+ // Expect a query with known answers
+ dispatchMessage();
+ final ArgumentCaptor<List<DatagramPacket>> knownAnswersQueryCaptor =
+ ArgumentCaptor.forClass(List.class);
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
+ knownAnswersQueryCaptor.capture(), eq(socketKey), eq(false));
+
+ final MdnsPacket knownAnswersQueryPacket = MdnsPacket.parse(
+ new MdnsPacketReader(knownAnswersQueryCaptor.getValue().get(0)));
+ assertTrue(hasQuestion(knownAnswersQueryPacket, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+ assertTrue(hasAnswer(knownAnswersQueryPacket, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+ assertFalse(hasAnswer(knownAnswersQueryPacket, MdnsRecord.TYPE_PTR, subtypeLabels));
+ }
+
+ @Test
+ public void testSendQueryWithSubTypeWithKnownAnswers() throws Exception {
+ client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache,
+ MdnsFeatureFlags.newBuilder().setIsQueryWithKnownAnswerEnabled(true).build());
+
+ doCallRealMethod().when(mockDeps).getDatagramPacketsFromMdnsPacket(
+ any(), any(MdnsPacket.class), any(InetSocketAddress.class), anyBoolean());
+
+ final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
+ .addSubtype("subtype").build();
+ startSendAndReceive(mockListenerOne, options);
+ InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
+
+ final ArgumentCaptor<List<DatagramPacket>> queryCaptor =
+ ArgumentCaptor.forClass(List.class);
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ // Send twice for IPv4 and IPv6
+ inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
+ queryCaptor.capture(), eq(socketKey), eq(false));
+ verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
+ assertNotNull(delayMessage);
+
+ final MdnsPacket queryPacket = MdnsPacket.parse(
+ new MdnsPacketReader(queryCaptor.getValue().get(0)));
+ final String[] subtypeLabels = Stream.concat(Stream.of("_subtype", "_sub"),
+ Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
+ assertTrue(hasQuestion(queryPacket, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+ assertTrue(hasQuestion(queryPacket, MdnsRecord.TYPE_PTR, subtypeLabels));
+
+ // Process a response
+ final String serviceName = "service-instance";
+ final String ipV4Address = "192.0.2.0";
+ final MdnsPacket packetWithoutSubtype = createResponse(
+ serviceName, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+ Collections.emptyMap() /* textAttributes */, TEST_TTL);
+ final MdnsPointerRecord originalPtr = (MdnsPointerRecord) CollectionUtils.findFirst(
+ packetWithoutSubtype.answers, r -> r instanceof MdnsPointerRecord);
+
+ // Add a subtype PTR record
+ final ArrayList<MdnsRecord> newAnswers = new ArrayList<>(packetWithoutSubtype.answers);
+ newAnswers.add(new MdnsPointerRecord(subtypeLabels, originalPtr.getReceiptTime(),
+ originalPtr.getCacheFlush(), originalPtr.getTtl(), originalPtr.getPointer()));
+ final MdnsPacket packetWithSubtype = new MdnsPacket(
+ packetWithoutSubtype.flags,
+ packetWithoutSubtype.questions,
+ newAnswers,
+ packetWithoutSubtype.authorityRecords,
+ packetWithoutSubtype.additionalRecords);
+ processResponse(packetWithSubtype, socketKey);
+
+ // Expect a query with known answers
+ dispatchMessage();
+ final ArgumentCaptor<List<DatagramPacket>> knownAnswersQueryCaptor =
+ ArgumentCaptor.forClass(List.class);
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
+ knownAnswersQueryCaptor.capture(), eq(socketKey), eq(false));
+
+ final MdnsPacket knownAnswersQueryPacket = MdnsPacket.parse(
+ new MdnsPacketReader(knownAnswersQueryCaptor.getValue().get(0)));
+ assertTrue(hasQuestion(knownAnswersQueryPacket, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+ assertTrue(hasQuestion(knownAnswersQueryPacket, MdnsRecord.TYPE_PTR, subtypeLabels));
+ assertTrue(hasAnswer(knownAnswersQueryPacket, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+ assertTrue(hasAnswer(knownAnswersQueryPacket, MdnsRecord.TYPE_PTR, subtypeLabels));
+ }
+
private static MdnsServiceInfo matchServiceName(String name) {
return argThat(info -> info.getServiceInstanceName().equals(name));
}
@@ -1967,17 +2083,21 @@
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
if (expectsUnicastResponse) {
verify(mockSocketClient).sendPacketRequestingUnicastResponse(
- expectedIPv4Packets[index], socketKey, false);
+ argThat(pkts -> pkts.get(0).equals(expectedIPv4Packets[index])),
+ eq(socketKey), eq(false));
if (multipleSocketDiscovery) {
verify(mockSocketClient).sendPacketRequestingUnicastResponse(
- expectedIPv6Packets[index], socketKey, false);
+ argThat(pkts -> pkts.get(0).equals(expectedIPv6Packets[index])),
+ eq(socketKey), eq(false));
}
} else {
verify(mockSocketClient).sendPacketRequestingMulticastResponse(
- expectedIPv4Packets[index], socketKey, false);
+ argThat(pkts -> pkts.get(0).equals(expectedIPv4Packets[index])),
+ eq(socketKey), eq(false));
if (multipleSocketDiscovery) {
verify(mockSocketClient).sendPacketRequestingMulticastResponse(
- expectedIPv6Packets[index], socketKey, false);
+ argThat(pkts -> pkts.get(0).equals(expectedIPv6Packets[index])),
+ eq(socketKey), eq(false));
}
}
verify(mockDeps, times(index + 1))
@@ -2006,6 +2126,12 @@
&& (name == null || Arrays.equals(q.name, name)));
}
+ private static boolean hasAnswer(MdnsPacket packet, int type, @NonNull String[] name) {
+ return packet.answers.stream().anyMatch(q -> {
+ return q.getType() == type && (Arrays.equals(q.name, name));
+ });
+ }
+
// A fake ScheduledExecutorService that keeps tracking the last scheduled Runnable and its delay
// time.
private class FakeExecutor extends ScheduledThreadPoolExecutor {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 8b7ab71..1989ed3 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -26,14 +26,18 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest.permission;
import android.annotation.RequiresPermission;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.text.format.DateUtils;
@@ -48,7 +52,9 @@
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -56,6 +62,8 @@
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -71,6 +79,7 @@
@Mock private Context mContext;
@Mock private WifiManager mockWifiManager;
+ @Mock private ConnectivityManager mockConnectivityManager;
@Mock private MdnsSocket mockMulticastSocket;
@Mock private MdnsSocket mockUnicastSocket;
@Mock private MulticastLock mockMulticastLock;
@@ -84,6 +93,9 @@
public void setup() throws RuntimeException, IOException {
MockitoAnnotations.initMocks(this);
+ doReturn(mockConnectivityManager).when(mContext).getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+
when(mockWifiManager.createMulticastLock(ArgumentMatchers.anyString()))
.thenReturn(mockMulticastLock);
@@ -226,7 +238,7 @@
// Sends a packet.
DatagramPacket packet = getTestDatagramPacket();
- mdnsClient.sendPacketRequestingMulticastResponse(packet,
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(packet),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
// mockMulticastSocket.send() will be called on another thread. If we verify it immediately,
// it may not be called yet. So timeout is added.
@@ -234,7 +246,7 @@
verify(mockUnicastSocket, timeout(TIMEOUT).times(0)).send(packet);
// Verify the packet is sent by the unicast socket.
- mdnsClient.sendPacketRequestingUnicastResponse(packet,
+ mdnsClient.sendPacketRequestingUnicastResponse(List.of(packet),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
verify(mockMulticastSocket, timeout(TIMEOUT).times(1)).send(packet);
verify(mockUnicastSocket, timeout(TIMEOUT).times(1)).send(packet);
@@ -279,7 +291,7 @@
// Sends a packet.
DatagramPacket packet = getTestDatagramPacket();
- mdnsClient.sendPacketRequestingMulticastResponse(packet,
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(packet),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
// mockMulticastSocket.send() will be called on another thread. If we verify it immediately,
// it may not be called yet. So timeout is added.
@@ -287,7 +299,7 @@
verify(mockUnicastSocket, timeout(TIMEOUT).times(0)).send(packet);
// Verify the packet is sent by the multicast socket as well.
- mdnsClient.sendPacketRequestingUnicastResponse(packet,
+ mdnsClient.sendPacketRequestingUnicastResponse(List.of(packet),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
verify(mockMulticastSocket, timeout(TIMEOUT).times(2)).send(packet);
verify(mockUnicastSocket, timeout(TIMEOUT).times(0)).send(packet);
@@ -320,19 +332,25 @@
@Test
public void testStartStop() throws IOException {
- for (int i = 0; i < 5; i++) {
+ for (int i = 1; i <= 5; i++) {
mdnsClient.startDiscovery();
Thread multicastReceiverThread = mdnsClient.multicastReceiveThread;
Thread socketThread = mdnsClient.sendThread;
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> cbCaptor =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
assertTrue(multicastReceiverThread.isAlive());
assertTrue(socketThread.isAlive());
+ verify(mockConnectivityManager, times(i))
+ .registerNetworkCallback(any(), cbCaptor.capture());
mdnsClient.stopDiscovery();
assertFalse(multicastReceiverThread.isAlive());
assertFalse(socketThread.isAlive());
+ verify(mockConnectivityManager, times(i))
+ .unregisterNetworkCallback(cbCaptor.getValue());
}
}
@@ -340,7 +358,7 @@
public void testStopDiscovery_queueIsCleared() throws IOException {
mdnsClient.startDiscovery();
mdnsClient.stopDiscovery();
- mdnsClient.sendPacketRequestingMulticastResponse(getTestDatagramPacket(),
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(getTestDatagramPacket()),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
synchronized (mdnsClient.multicastPacketQueue) {
@@ -352,7 +370,7 @@
public void testSendPacket_afterDiscoveryStops() throws IOException {
mdnsClient.startDiscovery();
mdnsClient.stopDiscovery();
- mdnsClient.sendPacketRequestingMulticastResponse(getTestDatagramPacket(),
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(getTestDatagramPacket()),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
synchronized (mdnsClient.multicastPacketQueue) {
@@ -366,7 +384,7 @@
//MdnsConfigsFlagsImpl.mdnsPacketQueueMaxSize.override(2L);
mdnsClient.startDiscovery();
for (int i = 0; i < 100; i++) {
- mdnsClient.sendPacketRequestingMulticastResponse(getTestDatagramPacket(),
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(getTestDatagramPacket()),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
}
@@ -464,9 +482,9 @@
mdnsClient.startDiscovery();
DatagramPacket packet = getTestDatagramPacket();
- mdnsClient.sendPacketRequestingUnicastResponse(packet,
+ mdnsClient.sendPacketRequestingUnicastResponse(List.of(packet),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
- mdnsClient.sendPacketRequestingMulticastResponse(packet,
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(packet),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
// Wait for the timer to be triggered.
@@ -497,9 +515,9 @@
assertFalse(mdnsClient.receivedUnicastResponse);
assertFalse(mdnsClient.cannotReceiveMulticastResponse.get());
- mdnsClient.sendPacketRequestingUnicastResponse(packet,
+ mdnsClient.sendPacketRequestingUnicastResponse(List.of(packet),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
- mdnsClient.sendPacketRequestingMulticastResponse(packet,
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(packet),
false /* onlyUseIpv6OnIpv6OnlyNetworks */);
Thread.sleep(MdnsConfigs.checkMulticastResponseIntervalMs() * 2);
@@ -556,6 +574,26 @@
.onResponseReceived(any(), argThat(key -> key.getInterfaceIndex() == -1));
}
+ @Test
+ public void testSendPacketWithMultipleDatagramPacket() throws IOException {
+ mdnsClient.startDiscovery();
+ final List<DatagramPacket> packets = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ packets.add(new DatagramPacket(new byte[10 + i] /* buff */, 0 /* offset */,
+ 10 + i /* length */, MdnsConstants.IPV4_SOCKET_ADDR));
+ }
+
+ // Sends packets.
+ mdnsClient.sendPacketRequestingMulticastResponse(packets,
+ false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ InOrder inOrder = inOrder(mockMulticastSocket);
+ for (int i = 0; i < 10; i++) {
+ // mockMulticastSocket.send() will be called on another thread. If we verify it
+ // immediately, it may not be called yet. So timeout is added.
+ inOrder.verify(mockMulticastSocket, timeout(TIMEOUT)).send(packets.get(i));
+ }
+ }
+
private DatagramPacket getTestDatagramPacket() {
return new DatagramPacket(buf, 0, 5,
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), 5353 /* port */));
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
index f705bcb..009205e 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
@@ -17,6 +17,13 @@
package com.android.server.connectivity.mdns.util
import android.os.Build
+import com.android.server.connectivity.mdns.MdnsConstants
+import com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED
+import com.android.server.connectivity.mdns.MdnsPacket
+import com.android.server.connectivity.mdns.MdnsPacketReader
+import com.android.server.connectivity.mdns.MdnsPointerRecord
+import com.android.server.connectivity.mdns.MdnsRecord
+import com.android.server.connectivity.mdns.util.MdnsUtils.createQueryDatagramPackets
import com.android.server.connectivity.mdns.util.MdnsUtils.equalsDnsLabelIgnoreDnsCase
import com.android.server.connectivity.mdns.util.MdnsUtils.equalsIgnoreDnsCase
import com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLabelsLowerCase
@@ -24,6 +31,8 @@
import com.android.server.connectivity.mdns.util.MdnsUtils.truncateServiceName
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import java.net.DatagramPacket
+import kotlin.test.assertContentEquals
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -43,19 +52,27 @@
assertEquals("ţést", toDnsLowerCase("ţést"))
// Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
// Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
- assertEquals("test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
- toDnsLowerCase("Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- "))
+ assertEquals(
+ "test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
+ toDnsLowerCase("Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ")
+ )
// Also test some characters where the first surrogate is not \ud800
- assertEquals("test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+ assertEquals(
+ "test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
"\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
- toDnsLowerCase("Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
- "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"))
+ toDnsLowerCase(
+ "Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+ "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"
+ )
+ )
}
@Test
fun testToDnsLabelsLowerCase() {
- assertArrayEquals(arrayOf("test", "tÉst", "ţést"),
- toDnsLabelsLowerCase(arrayOf("TeSt", "TÉST", "ţést")))
+ assertArrayEquals(
+ arrayOf("test", "tÉst", "ţést"),
+ toDnsLabelsLowerCase(arrayOf("TeSt", "TÉST", "ţést"))
+ )
}
@Test
@@ -67,13 +84,17 @@
assertFalse(equalsIgnoreDnsCase("ŢÉST", "ţést"))
// Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
// Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
- assertTrue(equalsIgnoreDnsCase("test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
- "Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- "))
+ assertTrue(equalsIgnoreDnsCase(
+ "test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
+ "Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- "
+ ))
// Also test some characters where the first surrogate is not \ud800
- assertTrue(equalsIgnoreDnsCase("test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+ assertTrue(equalsIgnoreDnsCase(
+ "test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
"\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
"Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
- "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"))
+ "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"
+ ))
}
@Test
@@ -92,14 +113,84 @@
@Test
fun testTypeEqualsOrIsSubtype() {
- assertTrue(MdnsUtils.typeEqualsOrIsSubtype(arrayOf("_type", "_tcp", "local"),
- arrayOf("_type", "_TCP", "local")))
- assertTrue(MdnsUtils.typeEqualsOrIsSubtype(arrayOf("_type", "_tcp", "local"),
- arrayOf("a", "_SUB", "_type", "_TCP", "local")))
- assertFalse(MdnsUtils.typeEqualsOrIsSubtype(arrayOf("_sub", "_type", "_tcp", "local"),
- arrayOf("_type", "_TCP", "local")))
+ assertTrue(MdnsUtils.typeEqualsOrIsSubtype(
+ arrayOf("_type", "_tcp", "local"),
+ arrayOf("_type", "_TCP", "local")
+ ))
+ assertTrue(MdnsUtils.typeEqualsOrIsSubtype(
+ arrayOf("_type", "_tcp", "local"),
+ arrayOf("a", "_SUB", "_type", "_TCP", "local")
+ ))
+ assertFalse(MdnsUtils.typeEqualsOrIsSubtype(
+ arrayOf("_sub", "_type", "_tcp", "local"),
+ arrayOf("_type", "_TCP", "local")
+ ))
assertFalse(MdnsUtils.typeEqualsOrIsSubtype(
arrayOf("a", "_other", "_type", "_tcp", "local"),
- arrayOf("a", "_SUB", "_type", "_TCP", "local")))
+ arrayOf("a", "_SUB", "_type", "_TCP", "local")
+ ))
+ }
+
+ @Test
+ fun testCreateQueryDatagramPackets() {
+ // Question data bytes:
+ // Name label(17)(duplicated labels) + PTR type(2) + cacheFlush(2) = 21
+ //
+ // Known answers data bytes:
+ // Name label(17)(duplicated labels) + PTR type(2) + cacheFlush(2) + receiptTimeMillis(4)
+ // + Data length(2) + Pointer data(18)(duplicated labels) = 45
+ val questions = mutableListOf<MdnsRecord>()
+ val knownAnswers = mutableListOf<MdnsRecord>()
+ for (i in 1..100) {
+ questions.add(MdnsPointerRecord(arrayOf("_testservice$i", "_tcp", "local"), false))
+ knownAnswers.add(MdnsPointerRecord(
+ arrayOf("_testservice$i", "_tcp", "local"),
+ 0L,
+ false,
+ 4_500_000L,
+ arrayOf("MyTestService$i", "_testservice$i", "_tcp", "local")
+ ))
+ }
+ // MdnsPacket data bytes:
+ // Questions(21 * 100) + Answers(45 * 100) = 6600 -> at least 5 packets
+ val query = MdnsPacket(
+ MdnsConstants.FLAGS_QUERY,
+ questions as List<MdnsRecord>,
+ knownAnswers as List<MdnsRecord>,
+ emptyList(),
+ emptyList()
+ )
+ // Expect the oversize MdnsPacket to be separated into 5 DatagramPackets.
+ val bufferSize = 1500
+ val packets = createQueryDatagramPackets(
+ ByteArray(bufferSize),
+ query,
+ MdnsConstants.IPV4_SOCKET_ADDR
+ )
+ assertEquals(5, packets.size)
+ assertTrue(packets.all { packet -> packet.length < bufferSize })
+
+ val mdnsPacket = createMdnsPacketFromMultipleDatagramPackets(packets)
+ assertEquals(query.flags, mdnsPacket.flags)
+ assertContentEquals(query.questions, mdnsPacket.questions)
+ assertContentEquals(query.answers, mdnsPacket.answers)
+ }
+
+ private fun createMdnsPacketFromMultipleDatagramPackets(
+ packets: List<DatagramPacket>
+ ): MdnsPacket {
+ var flags = 0
+ val questions = mutableListOf<MdnsRecord>()
+ val answers = mutableListOf<MdnsRecord>()
+ for ((index, packet) in packets.withIndex()) {
+ val mdnsPacket = MdnsPacket.parse(MdnsPacketReader(packet))
+ if (index != packets.size - 1) {
+ assertTrue((mdnsPacket.flags and FLAG_TRUNCATED) == FLAG_TRUNCATED)
+ }
+ flags = mdnsPacket.flags
+ questions.addAll(mdnsPacket.questions)
+ answers.addAll(mdnsPacket.answers)
+ }
+ return MdnsPacket(flags, questions, answers, emptyList(), emptyList())
}
}
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..3ad8de8
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
@@ -0,0 +1,422 @@
+/*
+ * 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
+ )
+ }
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // ConnectivityService might haven't finished checking blocked status for all requests.
+ waitForIdle()
+
+ // 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/CSCaptivePortalAppTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
index be2b29c..0bad60d 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
@@ -20,6 +20,7 @@
import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_DENIED
import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.net.CaptivePortal
import android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN
import android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL
import android.net.IpPrefix
@@ -33,23 +34,23 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.NetworkStack
-import android.net.CaptivePortal
import android.net.NetworkRequest
import android.net.NetworkScore
import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.net.NetworkStack
import android.net.RouteInfo
import android.os.Build
import android.os.Bundle
import androidx.test.filters.SmallTest
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.assertThrows
import com.android.testutils.TestableNetworkCallback
+import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import kotlin.test.assertEquals
// This allows keeping all the networks connected without having to file individual requests
// for them.
@@ -95,16 +96,22 @@
captivePortalCallback.expectAvailableCallbacksUnvalidated(wifiAgent)
val signInIntent = startCaptivePortalApp(wifiAgent)
// Remove the granted permissions
- context.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
- PERMISSION_DENIED)
+ context.setPermission(
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ PERMISSION_DENIED
+ )
context.setPermission(NETWORK_STACK, PERMISSION_DENIED)
val captivePortal: CaptivePortal? = signInIntent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL)
- assertThrows(SecurityException::class.java, { captivePortal?.reevaluateNetwork() })
+ captivePortal?.reevaluateNetwork()
+ verify(wifiAgent.networkMonitor, never()).forceReevaluation(anyInt())
}
private fun createWifiAgent(): CSAgentWrapper {
- return Agent(score = keepScore(), lp = lp(WIFI_IFACE),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ return Agent(
+ score = keepScore(),
+ lp = lp(WIFI_IFACE),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+ )
}
private fun startCaptivePortalApp(networkAgent: CSAgentWrapper): Intent {
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
new file mode 100644
index 0000000..cf990b1
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.connectivityservice
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.CALLBACK_CAP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_IP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_LOST
+import android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL
+import android.net.LinkAddress
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
+import android.net.NetworkRequest
+import android.os.Build
+import com.android.net.module.util.BitUtils.packBits
+import com.android.server.CSTest
+import com.android.server.ConnectivityService
+import com.android.server.defaultLp
+import com.android.server.defaultNc
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.tryTest
+import java.util.concurrent.atomic.AtomicInteger
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.spy
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+class CSDeclaredMethodsForCallbacksTest : CSTest() {
+ private val mockedCallbackFlags = AtomicInteger(DECLARED_METHODS_ALL)
+ private lateinit var wrappedService: ConnectivityService
+
+ private val instrumentedCm by lazy { ConnectivityManager(context, wrappedService) }
+
+ @Before
+ fun setUpWrappedService() {
+ // Mock the callback flags set by ConnectivityManager when calling ConnectivityService, to
+ // simulate methods not being overridden
+ wrappedService = spy(service)
+ doAnswer { inv ->
+ service.requestNetwork(
+ inv.getArgument(0),
+ inv.getArgument(1),
+ inv.getArgument(2),
+ inv.getArgument(3),
+ inv.getArgument(4),
+ inv.getArgument(5),
+ inv.getArgument(6),
+ inv.getArgument(7),
+ inv.getArgument(8),
+ inv.getArgument(9),
+ mockedCallbackFlags.get())
+ }.`when`(wrappedService).requestNetwork(
+ anyInt(),
+ any(),
+ anyInt(),
+ any(),
+ anyInt(),
+ any(),
+ anyInt(),
+ anyInt(),
+ any(),
+ any(),
+ anyInt()
+ )
+ doAnswer { inv ->
+ service.listenForNetwork(
+ inv.getArgument(0),
+ inv.getArgument(1),
+ inv.getArgument(2),
+ inv.getArgument(3),
+ inv.getArgument(4),
+ inv.getArgument(5),
+ mockedCallbackFlags.get()
+ )
+ }.`when`(wrappedService)
+ .listenForNetwork(any(), any(), any(), anyInt(), any(), any(), anyInt())
+ }
+
+ @Test
+ fun testCallbacksAreFiltered() {
+ val requestCb = TestableNetworkCallback()
+ val listenCb = TestableNetworkCallback()
+ mockedCallbackFlags.withFlags(CALLBACK_IP_CHANGED, CALLBACK_LOST) {
+ instrumentedCm.requestNetwork(NetworkRequest.Builder().build(), requestCb)
+ }
+ mockedCallbackFlags.withFlags(CALLBACK_CAP_CHANGED) {
+ instrumentedCm.registerNetworkCallback(NetworkRequest.Builder().build(), listenCb)
+ }
+
+ with(Agent()) {
+ connect()
+ sendLinkProperties(defaultLp().apply {
+ addLinkAddress(LinkAddress("fe80:db8::123/64"))
+ })
+ sendNetworkCapabilities(defaultNc().apply {
+ addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ })
+ disconnect()
+ }
+ waitForIdle()
+
+ // Only callbacks for the corresponding flags are called
+ requestCb.expect<CallbackEntry.LinkPropertiesChanged>()
+ requestCb.expect<CallbackEntry.Lost>()
+ requestCb.assertNoCallback(timeoutMs = 0L)
+
+ listenCb.expect<CallbackEntry.CapabilitiesChanged>()
+ listenCb.assertNoCallback(timeoutMs = 0L)
+ }
+}
+
+private fun AtomicInteger.withFlags(vararg flags: Int, action: () -> Unit) {
+ tryTest {
+ set(packBits(flags).toInt())
+ action()
+ } cleanup {
+ set(DECLARED_METHODS_ALL)
+ }
+}
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
new file mode 100644
index 0000000..83ccccd
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt
@@ -0,0 +1,198 @@
+/*
+ * 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.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
+import com.android.testutils.DevSdkIgnoreRule
+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
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.any
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class CSFirewallChainTest : CSTest() {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
+ // Tests for setFirewallChainEnabled on FIREWALL_CHAIN_BACKGROUND
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, false)])
+ fun setFirewallChainEnabled_backgroundChainDisabled() {
+ verifySetFirewallChainEnabledOnBackgroundDoesNothing()
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setFirewallChainEnabled_backgroundChainEnabled_afterU() {
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true)
+ verify(bpfNetMaps).setChildChain(FIREWALL_CHAIN_BACKGROUND, true)
+
+ clearInvocations(bpfNetMaps)
+
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
+ verify(bpfNetMaps).setChildChain(FIREWALL_CHAIN_BACKGROUND, false)
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setFirewallChainEnabled_backgroundChainEnabled_uptoU() {
+ verifySetFirewallChainEnabledOnBackgroundDoesNothing()
+ }
+
+ private fun verifySetFirewallChainEnabledOnBackgroundDoesNothing() {
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true)
+ verify(bpfNetMaps, never()).setChildChain(anyInt(), anyBoolean())
+
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
+ verify(bpfNetMaps, never()).setChildChain(anyInt(), anyBoolean())
+ }
+
+ // Tests for replaceFirewallChain on FIREWALL_CHAIN_BACKGROUND
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, false)])
+ fun replaceFirewallChain_backgroundChainDisabled() {
+ verifyReplaceFirewallChainOnBackgroundDoesNothing()
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun replaceFirewallChain_backgroundChainEnabled_afterU() {
+ val uids = intArrayOf(53, 42, 79)
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, uids)
+ verify(bpfNetMaps).replaceUidChain(FIREWALL_CHAIN_BACKGROUND, uids)
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun replaceFirewallChain_backgroundChainEnabled_uptoU() {
+ verifyReplaceFirewallChainOnBackgroundDoesNothing()
+ }
+
+ private fun verifyReplaceFirewallChainOnBackgroundDoesNothing() {
+ val uids = intArrayOf(53, 42, 79)
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, uids)
+ verify(bpfNetMaps, never()).replaceUidChain(anyInt(), any(IntArray::class.java))
+ }
+
+ // Tests for setUidFirewallRule on FIREWALL_CHAIN_BACKGROUND
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, false)])
+ fun setUidFirewallRule_backgroundChainDisabled() {
+ verifySetUidFirewallRuleOnBackgroundDoesNothing()
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setUidFirewallRule_backgroundChainEnabled_afterU() {
+ val uid = 2345
+
+ cm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DENY)
+
+ clearInvocations(bpfNetMaps)
+
+ cm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DENY)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DENY)
+
+ clearInvocations(bpfNetMaps)
+
+ cm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_ALLOW)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_ALLOW)
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setUidFirewallRule_backgroundChainEnabled_uptoU() {
+ verifySetUidFirewallRuleOnBackgroundDoesNothing()
+ }
+
+ private fun verifySetUidFirewallRuleOnBackgroundDoesNothing() {
+ val uid = 2345
+
+ 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
new file mode 100644
index 0000000..93f6e81
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
@@ -0,0 +1,313 @@
+/*
+ * 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 com.android.server
+
+import android.net.InetAddresses
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.VpnManager.TYPE_VPN_SERVICE
+import android.net.VpnTransportInfo
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.server.connectivity.ConnectivityFlags
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+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
+import org.mockito.Mockito.verify
+
+private const val VPN_IFNAME = "tun10041"
+private const val VPN_IFNAME2 = "tun10042"
+private const val WIFI_IFNAME = "wlan0"
+private const val TIMEOUT_MS = 1_000L
+private const val LONG_TIMEOUT_MS = 5_000
+
+private fun vpnNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_VPN)
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .setTransportInfo(
+ VpnTransportInfo(
+ TYPE_VPN_SERVICE,
+ "MySession12345",
+ false /* bypassable */,
+ false /* longLivedTcpConnectionsExpensive */
+ )
+ )
+ .build()
+
+private fun wifiNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+
+private fun nr(transport: Int) = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(transport).apply {
+ if (transport != TRANSPORT_VPN) {
+ addCapability(NET_CAPABILITY_NOT_VPN)
+ }
+ }.build()
+
+private fun lp(iface: String, vararg linkAddresses: LinkAddress) = LinkProperties().apply {
+ interfaceName = iface
+ for (linkAddress in linkAddresses) {
+ addLinkAddress(linkAddress)
+ }
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class CSIngressDiscardRuleTests : CSTest() {
+ private val IPV6_ADDRESS = InetAddresses.parseNumericAddress("2001:db8:1::1")
+ private val IPV6_LINK_ADDRESS = LinkAddress(IPV6_ADDRESS, 64)
+ private val IPV6_ADDRESS2 = InetAddresses.parseNumericAddress("2001:db8:1::2")
+ private val IPV6_LINK_ADDRESS2 = LinkAddress(IPV6_ADDRESS2, 64)
+ private val IPV6_ADDRESS3 = InetAddresses.parseNumericAddress("2001:db8:1::3")
+ private val IPV6_LINK_ADDRESS3 = LinkAddress(IPV6_ADDRESS3, 64)
+ 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
+ val wifiNc = wifiNc()
+ val wifiLp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS3)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+
+ val nr = nr(TRANSPORT_VPN)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(nr, cb)
+ val nc = vpnNc()
+ val lp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val agent = Agent(nc = nc, lp = lp)
+ agent.connect()
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+
+ // IngressDiscardRule is added to the VPN address
+ verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+ verify(bpfNetMaps, never()).setIngressDiscardRule(LOCAL_IPV6_ADDRRESS, VPN_IFNAME)
+
+ // The VPN address is changed
+ val newLp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS2, LOCAL_IPV6_LINK_ADDRRESS)
+ agent.sendLinkProperties(newLp)
+ cb.expect<LinkPropertiesChanged>(agent.network)
+
+ // IngressDiscardRule is removed from the old VPN address and added to the new VPN address
+ verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS)
+ verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS2, VPN_IFNAME)
+ verify(bpfNetMaps, never()).setIngressDiscardRule(LOCAL_IPV6_ADDRRESS, VPN_IFNAME)
+
+ agent.disconnect()
+ verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS2)
+
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_UpdateInterfaceName() {
+ val inorder = inOrder(bpfNetMaps)
+
+ val nr = nr(TRANSPORT_VPN)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(nr, cb)
+ val nc = vpnNc()
+ val lp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val agent = Agent(nc = nc, lp = lp)
+ agent.connect()
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+
+ // IngressDiscardRule is added to the VPN address
+ inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+ verifyNoMoreIngressDiscardRuleChange(inorder)
+
+ // The VPN interface name is changed
+ val newlp = lp(VPN_IFNAME2, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ agent.sendLinkProperties(newlp)
+ cb.expect<LinkPropertiesChanged>(agent.network)
+
+ // IngressDiscardRule is updated with the new interface name
+ inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME2)
+ verifyNoMoreIngressDiscardRuleChange(inorder)
+
+ agent.disconnect()
+ inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS)
+
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_DuplicatedIpAddress_UpdateVpnAddress() {
+ val inorder = inOrder(bpfNetMaps)
+
+ val wifiNc = wifiNc()
+ val wifiLp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+
+ // IngressDiscardRule is not added to non-VPN interfaces
+ inorder.verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+
+ val nr = nr(TRANSPORT_VPN)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+ val vpnNc = vpnNc()
+ val vpnLp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val vpnAgent = Agent(nc = vpnNc, lp = vpnLp)
+ vpnAgent.connect()
+ cb.expectAvailableCallbacks(vpnAgent.network, validated = false)
+
+ // IngressDiscardRule is not added since the VPN address is duplicated with the Wi-Fi
+ // address
+ inorder.verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+
+ // The VPN address is changed to a different address from the Wi-Fi interface
+ val newVpnlp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS2, LOCAL_IPV6_LINK_ADDRRESS)
+ vpnAgent.sendLinkProperties(newVpnlp)
+
+ // IngressDiscardRule is added to the VPN address since the VPN address is not duplicated
+ // with the Wi-Fi address
+ cb.expect<LinkPropertiesChanged>(vpnAgent.network)
+ inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS2, VPN_IFNAME)
+
+ // The VPN address is changed back to the same address as the Wi-Fi interface
+ vpnAgent.sendLinkProperties(vpnLp)
+ cb.expect<LinkPropertiesChanged>(vpnAgent.network)
+
+ // 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)
+ verifyNoMoreIngressDiscardRuleChange(inorder)
+
+ vpnAgent.disconnect()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
+
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_DuplicatedIpAddress_UpdateNonVpnAddress() {
+ val inorder = inOrder(bpfNetMaps)
+
+ val vpnNc = vpnNc()
+ val vpnLp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val vpnAgent = Agent(nc = vpnNc, lp = vpnLp)
+ vpnAgent.connect()
+
+ // IngressDiscardRule is added to the VPN address
+ inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+ verifyNoMoreIngressDiscardRuleChange(inorder)
+
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+ val wifiNc = wifiNc()
+ val wifiLp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // IngressDiscardRule is removed since the VPN address is duplicated with the Wi-Fi address
+ inorder.verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS)
+
+ // The Wi-Fi address is changed to a different address from the VPN interface
+ val newWifilp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS2, LOCAL_IPV6_LINK_ADDRRESS)
+ wifiAgent.sendLinkProperties(newWifilp)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // 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)
+ verifyNoMoreIngressDiscardRuleChange(inorder)
+
+ // The Wi-Fi address is changed back to the same address as the VPN interface
+ wifiAgent.sendLinkProperties(wifiLp)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // IngressDiscardRule is removed since the VPN address is duplicated with the Wi-Fi address
+ inorder.verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS)
+
+ // IngressDiscardRule is added to the VPN address since Wi-Fi is disconnected
+ wifiAgent.disconnect()
+ inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS))
+ .setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+
+ vpnAgent.disconnect()
+ inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS)
+
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_UnregisterAfterReplacement() {
+ val wifiNc = wifiNc()
+ val wifiLp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+ waitForIdle()
+
+ val vpnNc = vpnNc()
+ val vpnLp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val vpnAgent = Agent(nc = vpnNc, lp = vpnLp)
+ vpnAgent.connect()
+
+ // IngressDiscardRule is added since the Wi-Fi network is destroyed
+ verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+
+ // IngressDiscardRule is removed since the VPN network is destroyed
+ vpnAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+ waitForIdle()
+ verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS)
+ }
+
+ @Test @FeatureFlags([Flag(ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING, false)])
+ fun testVpnIngressDiscardRule_FeatureDisabled() {
+ val nr = nr(TRANSPORT_VPN)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(nr, cb)
+ val nc = vpnNc()
+ val lp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val agent = Agent(nc = nc, lp = lp)
+ agent.connect()
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+
+ // IngressDiscardRule should not be added since feature is disabled
+ verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
index c1730a4..83fff87 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
@@ -38,6 +38,7 @@
import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
import android.net.NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK
import android.net.RouteInfo
+import android.net.connectivity.ConnectivityCompatChanges.ENABLE_MATCH_LOCAL_NETWORK
import android.os.Build
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
@@ -47,12 +48,15 @@
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.eq
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.timeout
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
private const val TIMEOUT_MS = 200L
private const val MEDIUM_TIMEOUT_MS = 1_000L
@@ -88,10 +92,10 @@
class CSLocalAgentTests : CSTest() {
val multicastRoutingConfigMinScope =
MulticastRoutingConfig.Builder(MulticastRoutingConfig.FORWARD_WITH_MIN_SCOPE, 4)
- .build();
+ .build()
val multicastRoutingConfigSelected =
MulticastRoutingConfig.Builder(MulticastRoutingConfig.FORWARD_SELECTED)
- .build();
+ .build()
val upstreamSelectorAny = NetworkRequest.Builder()
.addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
.build()
@@ -205,6 +209,9 @@
nc = nc(TRANSPORT_THREAD, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp(name),
lnc = localNetworkConfig,
+ score = FromS(NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build())
)
return localAgent
}
@@ -219,9 +226,12 @@
nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET))
}
- private fun sendLocalNetworkConfig(localAgent: CSAgentWrapper,
- upstreamSelector: NetworkRequest?, upstreamConfig: MulticastRoutingConfig,
- downstreamConfig: MulticastRoutingConfig) {
+ private fun sendLocalNetworkConfig(
+ localAgent: CSAgentWrapper,
+ upstreamSelector: NetworkRequest?,
+ upstreamConfig: MulticastRoutingConfig,
+ downstreamConfig: MulticastRoutingConfig
+ ) {
val newLnc = LocalNetworkConfig.Builder()
.setUpstreamSelector(upstreamSelector)
.setUpstreamMulticastRoutingConfig(upstreamConfig)
@@ -458,7 +468,6 @@
wifiAgent.disconnect()
}
-
@Test
fun testUnregisterUpstreamAfterReplacement_SameIfaceName() {
doTestUnregisterUpstreamAfterReplacement(true)
@@ -824,4 +833,59 @@
listenCb.expect<Lost>()
}
+
+ fun doTestLocalNetworkRequest(
+ request: NetworkRequest,
+ enableMatchLocalNetwork: Boolean,
+ expectCallback: Boolean
+ ) {
+ deps.setBuildSdk(VERSION_V)
+ deps.setChangeIdEnabled(enableMatchLocalNetwork, ENABLE_MATCH_LOCAL_NETWORK)
+
+ val requestCb = TestableNetworkCallback()
+ val listenCb = TestableNetworkCallback()
+ cm.requestNetwork(request, requestCb)
+ cm.registerNetworkCallback(request, listenCb)
+
+ val localAgent = createLocalAgent("local0", FromS(LocalNetworkConfig.Builder().build()))
+ localAgent.connect()
+
+ if (expectCallback) {
+ requestCb.expectAvailableCallbacks(localAgent.network, validated = false)
+ listenCb.expectAvailableCallbacks(localAgent.network, validated = false)
+ } else {
+ waitForIdle()
+ requestCb.assertNoCallback(timeoutMs = 0)
+ listenCb.assertNoCallback(timeoutMs = 0)
+ }
+ localAgent.disconnect()
+ }
+
+ @Test
+ fun testLocalNetworkRequest() {
+ val request = NetworkRequest.Builder().build()
+ // If ENABLE_MATCH_LOCAL_NETWORK is false, request is not satisfied by local network
+ doTestLocalNetworkRequest(
+ request,
+ enableMatchLocalNetwork = false,
+ expectCallback = false)
+ // If ENABLE_MATCH_LOCAL_NETWORK is true, request is satisfied by local network
+ doTestLocalNetworkRequest(
+ request,
+ enableMatchLocalNetwork = true,
+ expectCallback = true)
+ }
+
+ @Test
+ fun testLocalNetworkRequest_withCapability() {
+ val request = NetworkRequest.Builder().addCapability(NET_CAPABILITY_LOCAL_NETWORK).build()
+ doTestLocalNetworkRequest(
+ request,
+ enableMatchLocalNetwork = false,
+ expectCallback = true)
+ doTestLocalNetworkRequest(
+ request,
+ enableMatchLocalNetwork = true,
+ expectCallback = true)
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
similarity index 69%
rename from tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
rename to tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
index 9024641..88c2738 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
@@ -24,6 +24,7 @@
import android.net.NativeNetworkType
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
@@ -41,10 +42,13 @@
import android.util.ArraySet
import com.android.net.module.util.CollectionUtils
import com.android.server.ConnectivityService.PREFERENCE_ORDER_SATELLITE_FALLBACK
+import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.TestableNetworkCallback
import com.android.testutils.visibleOnHandlerThread
import org.junit.Assert
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
@@ -61,7 +65,10 @@
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
-class CSSatelliteNetworkPreferredTest : CSTest() {
+class CSSatelliteNetworkTest : CSTest() {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
/**
* Test createMultiLayerNrisFromSatelliteNetworkPreferredUids returns correct
* NetworkRequestInfo.
@@ -80,54 +87,81 @@
}
/**
- * Test that SATELLITE_NETWORK_PREFERENCE_UIDS changes will send correct net id and uid ranges
- * to netd.
+ * Test that satellite network satisfies satellite fallback per-app default network request and
+ * send correct net id and uid ranges to netd.
*/
- @Test
- fun testSatelliteNetworkPreferredUidsChanged() {
+ private fun doTestSatelliteNetworkFallbackUids(restricted: Boolean) {
val netdInOrder = inOrder(netd)
- val satelliteAgent = createSatelliteAgent("satellite0")
+ val satelliteAgent = createSatelliteAgent("satellite0", restricted)
satelliteAgent.connect()
val satelliteNetId = satelliteAgent.network.netId
+ val permission = if (restricted) {INetd.PERMISSION_SYSTEM} else {INetd.PERMISSION_NONE}
netdInOrder.verify(netd).networkCreate(
- nativeNetworkConfigPhysical(satelliteNetId, INetd.PERMISSION_NONE))
+ nativeNetworkConfigPhysical(satelliteNetId, permission))
val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
- // Initial satellite network preferred uids status.
- setAndUpdateSatelliteNetworkPreferredUids(setOf())
+ // Initial satellite network fallback uids status.
+ updateSatelliteNetworkFallbackUids(setOf())
netdInOrder.verify(netd, never()).networkAddUidRangesParcel(any())
netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
- // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting and verify that net id and uid ranges
- // send to netd
+ // Update satellite network fallback uids and verify that net id and uid ranges send to netd
var uids = mutableSetOf(uid1, uid2, uid3)
val uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids))
val config1 = NativeUidRangeConfig(
satelliteNetId, uidRanges1,
PREFERENCE_ORDER_SATELLITE_FALLBACK
)
- setAndUpdateSatelliteNetworkPreferredUids(uids)
+ updateSatelliteNetworkFallbackUids(uids)
netdInOrder.verify(netd).networkAddUidRangesParcel(config1)
netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
- // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting again and verify that old rules are removed
- // and new rules are added.
+ // Update satellite network fallback uids and verify that net id and uid ranges send to netd
uids = mutableSetOf(uid1)
val uidRanges2: Array<UidRangeParcel?> = toUidRangeStableParcels(uidRangesForUids(uids))
val config2 = NativeUidRangeConfig(
satelliteNetId, uidRanges2,
PREFERENCE_ORDER_SATELLITE_FALLBACK
)
- setAndUpdateSatelliteNetworkPreferredUids(uids)
+ updateSatelliteNetworkFallbackUids(uids)
netdInOrder.verify(netd).networkRemoveUidRangesParcel(config1)
netdInOrder.verify(netd).networkAddUidRangesParcel(config2)
}
+ @Test
+ fun testSatelliteNetworkFallbackUids_restricted() {
+ doTestSatelliteNetworkFallbackUids(restricted = true)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSatelliteNetworkFallbackUids_nonRestricted() {
+ doTestSatelliteNetworkFallbackUids(restricted = false)
+ }
+
+ private fun doTestSatelliteNeverBecomeDefaultNetwork(restricted: Boolean) {
+ val agent = createSatelliteAgent("satellite0", restricted)
+ agent.connect()
+ val defaultCb = TestableNetworkCallback()
+ cm.registerDefaultNetworkCallback(defaultCb)
+ // Satellite network must not become the default network
+ defaultCb.assertNoCallback()
+ }
+
+ @Test
+ fun testSatelliteNeverBecomeDefaultNetwork_restricted() {
+ doTestSatelliteNeverBecomeDefaultNetwork(restricted = true)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSatelliteNeverBecomeDefaultNetwork_notRestricted() {
+ doTestSatelliteNeverBecomeDefaultNetwork(restricted = false)
+ }
+
private fun assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(uids: Set<Int>) {
val nris: Set<ConnectivityService.NetworkRequestInfo> =
service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(uids)
@@ -140,7 +174,7 @@
assertEquals(PREFERENCE_ORDER_SATELLITE_FALLBACK, nri.mPreferenceOrder)
}
- private fun setAndUpdateSatelliteNetworkPreferredUids(uids: Set<Int>) {
+ private fun updateSatelliteNetworkFallbackUids(uids: Set<Int>) {
visibleOnHandlerThread(csHandler) {
deps.satelliteNetworkFallbackUidUpdate!!.accept(uids)
}
@@ -150,9 +184,9 @@
NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission,
false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
- private fun createSatelliteAgent(name: String): CSAgentWrapper {
+ private fun createSatelliteAgent(name: String, restricted: Boolean = true): CSAgentWrapper {
return Agent(score = keepScore(), lp = lp(name),
- nc = nc(TRANSPORT_SATELLITE, NET_CAPABILITY_INTERNET)
+ nc = satelliteNc(restricted)
)
}
@@ -176,17 +210,19 @@
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(restricted: Boolean) =
+ 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)
+ if (restricted) {
+ removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ }
+ removeCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ }.build()
private fun lp(iface: String) = LinkProperties().apply {
interfaceName = iface
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index d7343b1..13c5cbc 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -28,6 +28,7 @@
import android.net.NetworkAgent
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkProvider
@@ -39,6 +40,9 @@
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkCallback
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.assertEquals
+import kotlin.test.fail
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
@@ -46,9 +50,6 @@
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.verify
import org.mockito.stubbing.Answer
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.test.assertEquals
-import kotlin.test.fail
const val SHORT_TIMEOUT_MS = 200L
@@ -140,6 +141,9 @@
val request = NetworkRequest.Builder().apply {
clearCapabilities()
if (nc.transportTypes.isNotEmpty()) addTransportType(nc.transportTypes[0])
+ if (nc.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)) {
+ addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ }
}.build()
val cb = TestableNetworkCallback()
mgr.registerNetworkCallback(request, cb)
@@ -166,6 +170,9 @@
val request = NetworkRequest.Builder().apply {
clearCapabilities()
if (nc.transportTypes.isNotEmpty()) addTransportType(nc.transportTypes[0])
+ if (nc.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)) {
+ addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ }
}.build()
val cb = TestableNetworkCallback(timeoutMs = SHORT_TIMEOUT_MS)
mgr.registerNetworkCallback(request, cb)
@@ -178,6 +185,7 @@
fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
fun sendNetworkCapabilities(nc: NetworkCapabilities) = agent.sendNetworkCapabilities(nc)
+ fun sendLinkProperties(lp: LinkProperties) = agent.sendLinkProperties(lp)
fun connectWithCaptivePortal(redirectUrl: String) {
setCaptivePortal(redirectUrl)
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 595ca47..ed72fd2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -17,6 +17,7 @@
package com.android.server
import android.app.AlarmManager
+import android.app.AppOpsManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -26,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
@@ -42,6 +44,7 @@
import android.net.NetworkProvider
import android.net.NetworkScore
import android.net.PacProxyManager
+import android.net.connectivity.ConnectivityCompatChanges.ENABLE_MATCH_LOCAL_NETWORK
import android.net.networkstack.NetworkStackClientBase
import android.os.BatteryStatsManager
import android.os.Bundle
@@ -51,9 +54,9 @@
import android.os.UserHandle
import android.os.UserManager
import android.permission.PermissionManager.PermissionResult
+import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.testing.TestableContext
-import android.util.ArraySet
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.app.IBatteryStats
import com.android.internal.util.test.BroadcastInterceptingContext
@@ -75,14 +78,19 @@
import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
-import java.util.function.Consumer
import java.util.function.BiConsumer
+import java.util.function.Consumer
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.fail
import org.junit.After
import org.junit.Before
+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
@@ -103,6 +111,8 @@
internal const val VERSION_V = 5
internal const val VERSION_MAX = VERSION_V
+internal const val CALLING_UID_UNMOCKED = Process.INVALID_UID
+
private fun NetworkCapabilities.getLegacyType() =
when (transportTypes.getOrElse(0) { TRANSPORT_WIFI }) {
TRANSPORT_BLUETOOTH -> ConnectivityManager.TYPE_BLUETOOTH
@@ -122,14 +132,19 @@
// TODO (b/272685721) : make ConnectivityServiceTest smaller and faster by moving the setup
// parts into this class and moving the individual tests to multiple separate classes.
open class CSTest {
+ @get:Rule
+ val testNameRule = TestName()
+
companion object {
val CSTestExecutor = Executors.newSingleThreadExecutor()
}
init {
if (!SdkLevel.isAtLeastS()) {
- throw UnsupportedApiLevelException("CSTest subclasses must be annotated to only " +
- "run on S+, e.g. @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)")
+ throw UnsupportedApiLevelException(
+ "CSTest subclasses must be annotated to only " +
+ "run on S+, e.g. @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)"
+ )
}
}
@@ -144,13 +159,14 @@
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.LOG_BPF_RC] = 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 enableFeature(f: String) = enabledFeatures.set(f, true)
- fun disableFeature(f: String) = enabledFeatures.set(f, false)
+ fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
// When adding new members, consider if it's not better to build the object in CSTestHelpers
// to keep this file clean of implementation details. Generally, CSTestHelpers should only
@@ -158,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()
@@ -170,19 +190,28 @@
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)
+ val appOpsManager = mock<AppOpsManager>()
val telephonyManager = mock<TelephonyManager>().also {
doReturn(true).`when`(it).isDataCapable()
}
+ val subscriptionManager = mock<SubscriptionManager>()
val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
val satelliteAccessController = mock<SatelliteAccessController>()
+ val destroySocketsWrapper = mock<DestroySocketsWrapper>()
val deps = CSDeps()
@@ -193,8 +222,32 @@
lateinit var cm: ConnectivityManager
lateinit var csHandler: Handler
+ // Tests can use this annotation to set flag values before constructing ConnectivityService
+ // e.g. @FeatureFlags([Flag(flagName1, true/false), Flag(flagName2, true/false)])
+ @Retention(RUNTIME)
+ @Target(FUNCTION)
+ annotation class FeatureFlags(val flags: Array<Flag>)
+
+ @Retention(RUNTIME)
+ @Target(FUNCTION)
+ annotation class Flag(val name: String, val enabled: Boolean)
+
@Before
fun setUp() {
+ // Set feature flags before constructing ConnectivityService
+ val testMethodName = testNameRule.methodName
+ try {
+ val testMethod = this::class.java.getMethod(testMethodName)
+ val featureFlags = testMethod.getAnnotation(FeatureFlags::class.java)
+ if (featureFlags != null) {
+ for (flag in featureFlags.flags) {
+ setFeatureEnabled(flag.name, flag.enabled)
+ }
+ }
+ } catch (ignored: NoSuchMethodException) {
+ // This is expected for parameterized tests
+ }
+
alarmHandlerThread = HandlerThread("TestAlarmManager").also { it.start() }
alarmManager = makeMockAlarmManager(alarmHandlerThread)
service = makeConnectivityService(context, netd, deps).also { it.systemReadyInternal() }
@@ -212,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
@@ -227,7 +285,8 @@
context: Context,
tm: TelephonyManager,
requestRestrictedWifiEnabled: Boolean,
- listener: BiConsumer<Int, Int>
+ listener: BiConsumer<Int, Int>,
+ handler: Handler
) = if (SdkLevel.isAtLeastT()) mock<CarrierPrivilegeAuthenticator>() else null
var satelliteNetworkFallbackUidUpdate: Consumer<Set<Int>>? = null
@@ -249,8 +308,12 @@
AutomaticOnOffKeepaliveTracker(c, h, AOOKTDeps(c))
override fun makeMultinetworkPolicyTracker(c: Context, h: Handler, r: Runnable) =
- MultinetworkPolicyTracker(c, h, r,
- MultinetworkPolicyTrackerTestDependencies(connResources.get()))
+ MultinetworkPolicyTracker(
+ c,
+ h,
+ r,
+ MultinetworkPolicyTrackerTestDependencies(connResources.get())
+ )
override fun makeNetworkRequestStateStatsMetrics(c: Context) =
this@CSTest.networkRequestStateStatsMetrics
@@ -264,7 +327,7 @@
enabledFeatures[name] ?: fail("Unmocked feature $name, see CSTest.enabledFeatures")
// Mocked change IDs
- private val enabledChangeIds = ArraySet<Long>()
+ private val enabledChangeIds = arrayListOf(ENABLE_MATCH_LOCAL_NETWORK)
fun setChangeIdEnabled(enabled: Boolean, changeId: Long) {
// enabledChangeIds is read on the handler thread and maybe the test thread, so
// make sure both threads see it before continuing.
@@ -299,6 +362,24 @@
override fun isAtLeastT() = if (isSdkUnmocked) super.isAtLeastT() else sdkLevel >= VERSION_T
override fun isAtLeastU() = if (isSdkUnmocked) super.isAtLeastU() else sdkLevel >= VERSION_U
override fun isAtLeastV() = if (isSdkUnmocked) super.isAtLeastV() else sdkLevel >= VERSION_V
+
+ private var callingUid = CALLING_UID_UNMOCKED
+
+ fun unmockCallingUid() {
+ setCallingUid(CALLING_UID_UNMOCKED)
+ }
+
+ fun setCallingUid(callingUid: Int) {
+ visibleOnHandlerThread(csHandler) { this.callingUid = callingUid }
+ }
+
+ 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) {
@@ -322,8 +403,12 @@
override fun enforceCallingOrSelfPermission(permission: String, message: String?) {
// If the permission result does not set in the mMockedPermissions, it will be
// considered as PERMISSION_GRANTED as existing design to prevent breaking other tests.
- val granted = checkMockedPermission(permission, Process.myPid(), Process.myUid(),
- PERMISSION_GRANTED)
+ val granted = checkMockedPermission(
+ permission,
+ Process.myPid(),
+ Process.myUid(),
+ PERMISSION_GRANTED
+ )
if (!granted.equals(PERMISSION_GRANTED)) {
throw SecurityException("[Test] permission denied: " + permission)
}
@@ -334,8 +419,12 @@
override fun checkCallingOrSelfPermission(permission: String) =
checkMockedPermission(permission, Process.myPid(), Process.myUid(), PERMISSION_GRANTED)
- private fun checkMockedPermission(permission: String, pid: Int, uid: Int, default: Int):
- Int {
+ private fun checkMockedPermission(
+ permission: String,
+ pid: Int,
+ uid: Int,
+ default: Int
+ ): Int {
val processSpecificKey = "$permission,$pid,$uid"
return mMockedPermissions[processSpecificKey]
?: mMockedPermissions[permission] ?: default
@@ -397,16 +486,17 @@
Context.ACTIVITY_SERVICE -> activityManager
Context.SYSTEM_CONFIG_SERVICE -> systemConfigManager
Context.TELEPHONY_SERVICE -> telephonyManager
+ Context.TELEPHONY_SUBSCRIPTION_SERVICE -> subscriptionManager
Context.BATTERY_STATS_SERVICE -> batteryManager
Context.STATS_MANAGER -> null // Stats manager is final and can't be mocked
+ Context.APP_OPS_SERVICE -> appOpsManager
else -> super.getSystemService(serviceName)
}
internal val orderedBroadcastAsUserHistory = ArrayTrackRecord<Intent>().newReadHead()
fun expectNoDataActivityBroadcast(timeoutMs: Int) {
- assertNull(orderedBroadcastAsUserHistory.poll(
- timeoutMs.toLong()) { intent -> true })
+ assertNull(orderedBroadcastAsUserHistory.poll(timeoutMs.toLong()))
}
override fun sendOrderedBroadcastAsUser(
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetInterfaceStateMachineTest.kt b/tests/unit/java/com/android/server/ethernet/EthernetInterfaceStateMachineTest.kt
new file mode 100644
index 0000000..c8b2f65
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetInterfaceStateMachineTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+// 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 com.android.server.ethernet
+
+import android.content.Context
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.NetworkProvider.NetworkOfferCallback
+import android.os.Build
+import android.os.Handler
+import android.os.test.TestLooper
+import androidx.test.filters.SmallTest
+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.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+private const val IFACE = "eth0"
+private val CAPS = NetworkCapabilities.Builder().build()
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class EthernetInterfaceStateMachineTest {
+ private lateinit var looper: TestLooper
+ private lateinit var handler: Handler
+ private lateinit var ifaceState: EthernetInterfaceStateMachine
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var provider: NetworkProvider
+ @Mock private lateinit var deps: EthernetNetworkFactory.Dependencies
+
+ // There seems to be no (obvious) way to force execution of @Before and @Test annotation on the
+ // same thread. Since SyncStateMachine requires all interactions to be called from the same
+ // thread that is provided at construction time (in this case, the thread that TestLooper() is
+ // called on), setUp() must be called directly from the @Test method.
+ // TODO: find a way to fix this in the test runner.
+ fun setUp() {
+ looper = TestLooper()
+ handler = Handler(looper.looper)
+ MockitoAnnotations.initMocks(this)
+
+ ifaceState = EthernetInterfaceStateMachine(IFACE, handler, context, CAPS, provider, deps)
+ }
+
+ @Test
+ fun testUpdateLinkState_networkOfferRegisteredAndRetracted() {
+ setUp()
+
+ ifaceState.updateLinkState(/* up= */ true)
+
+ // link comes up: validate the NetworkOffer is registered and capture callback object.
+ val inOrder = inOrder(provider)
+ val networkOfferCb = ArgumentCaptor.forClass(NetworkOfferCallback::class.java).also {
+ inOrder.verify(provider).registerNetworkOffer(any(), any(), any(), it.capture())
+ }.value
+
+ ifaceState.updateLinkState(/* up */ false)
+
+ // link goes down: validate the NetworkOffer is retracted
+ inOrder.verify(provider).unregisterNetworkOffer(eq(networkOfferCb))
+ }
+}
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 2be74db..7e0a225 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;
@@ -68,11 +69,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;
@@ -118,6 +122,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;
@@ -163,12 +168,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;
@@ -193,6 +201,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}.
@@ -206,6 +215,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;
@@ -300,6 +310,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;
@@ -410,8 +431,6 @@
mElapsedRealtime = 0L;
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
// Verify that system ready fetches realtime stats
@@ -450,6 +469,7 @@
class TestDependencies extends NetworkStatsService.Dependencies {
private int mCompareStatsInvocation = 0;
+ private NetworkStats.Entry mMockedTrafficStatsNativeStat = null;
@Override
public File getLegacyStatsDir() {
@@ -591,6 +611,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
@@ -747,8 +812,6 @@
assertStatsFilesExist(true);
// boot through serviceReady() again
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
@@ -947,7 +1010,16 @@
}
@Test
- public void testMobileStatsByRatType() throws Exception {
+ public void testMobileStatsByRatTypeForSatellite() throws Exception {
+ doTestMobileStatsByRatType(new NetworkStateSnapshot[]{buildSatelliteMobileState(IMSI_1)});
+ }
+
+ @Test
+ public void testMobileStatsByRatTypeForCellular() throws Exception {
+ doTestMobileStatsByRatType(new NetworkStateSnapshot[]{buildMobileState(IMSI_1)});
+ }
+
+ private void doTestMobileStatsByRatType(NetworkStateSnapshot[] states) throws Exception {
final NetworkTemplate template3g = new NetworkTemplate.Builder(MATCH_MOBILE)
.setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
.setMeteredness(METERED_YES).build();
@@ -957,8 +1029,6 @@
final NetworkTemplate template5g = new NetworkTemplate.Builder(MATCH_MOBILE)
.setRatType(TelephonyManager.NETWORK_TYPE_NR)
.setMeteredness(METERED_YES).build();
- final NetworkStateSnapshot[] states =
- new NetworkStateSnapshot[]{buildMobileState(IMSI_1)};
// 3G network comes online.
mockNetworkStatsSummary(buildEmptyStats());
@@ -972,7 +1042,7 @@
incrementCurrentTime(MINUTE_IN_MILLIS);
mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12L, 18L, 14L, 1L, 0L)));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12L, 18L, 14L, 1L, 0L)));
forcePollAndWaitForIdle();
// Verify 3g templates gets stats.
@@ -987,7 +1057,7 @@
mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
// Append more traffic on existing 3g stats entry.
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16L, 22L, 17L, 2L, 0L))
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16L, 22L, 17L, 2L, 0L))
// Add entry that is new on 4g.
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 33L, 27L, 8L, 10L, 1L)));
@@ -1389,6 +1459,57 @@
}
@Test
+ public void testGetUidStatsForTransportWithCellularAndSatellite() throws Exception {
+ // Setup satellite mobile network and Cellular mobile network
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(buildEmptyStats());
+
+ final NetworkStateSnapshot mobileState = buildStateOfTransport(
+ NetworkCapabilities.TRANSPORT_CELLULAR, TYPE_MOBILE,
+ TEST_IFACE2, IMSI_1, null /* wifiNetworkKey */,
+ false /* isTemporarilyNotMetered */, false /* isRoaming */);
+
+ final NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{mobileState,
+ buildSatelliteMobileState(IMSI_1)};
+ mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
+ new UnderlyingNetworkInfo[0]);
+ setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_LTE);
+
+ // mock traffic on satellite network
+ final NetworkStats.Entry entrySatellite = new NetworkStats.Entry(
+ TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 80L, 5L, 70L, 15L, 1L);
+
+ // mock traffic on cellular network
+ final NetworkStats.Entry entryCellular = new NetworkStats.Entry(
+ TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 100L, 15L, 150L, 15L, 1L);
+
+ final TetherStatsParcel[] emptyTetherStats = {};
+ // The interfaces that expect to be used to query the stats.
+ final String[] mobileIfaces = {TEST_IFACE, TEST_IFACE2};
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+ .insertEntry(entrySatellite).insertEntry(entryCellular), emptyTetherStats,
+ mobileIfaces);
+ // with getUidStatsForTransport(TRANSPORT_CELLULAR) return stats of both cellular
+ // and satellite
+ final NetworkStats mobileStats = mService.getUidStatsForTransport(
+ NetworkCapabilities.TRANSPORT_CELLULAR);
+
+ // The iface field of the returned stats should be null because getUidStatsForTransport
+ // clears the interface field before it returns the result.
+ assertValues(mobileStats, null /* iface */, UID_RED, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, METERED_NO, 180L, 20L, 220L, 30L, 2L);
+
+ // getUidStatsForTransport(TRANSPORT_SATELLITE) is not supported
+ assertThrows(IllegalArgumentException.class,
+ () -> mService.getUidStatsForTransport(NetworkCapabilities.TRANSPORT_SATELLITE));
+
+ }
+
+ @Test
public void testForegroundBackground() throws Exception {
// pretend that network comes online
mockDefaultSettings();
@@ -2071,8 +2192,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);
@@ -2084,8 +2203,6 @@
assertStatsFilesExist(false);
// Boot through systemReady() again.
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
@@ -2159,8 +2276,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);
@@ -2172,8 +2287,6 @@
assertStatsFilesExist(false);
// Boot through systemReady() again.
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
@@ -2325,6 +2438,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());
@@ -2389,6 +2580,8 @@
}
private void prepareForSystemReady() throws Exception {
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(buildEmptyStats());
mockNetworkStatsSummary(buildEmptyStats());
}
@@ -2546,6 +2739,12 @@
false /* isTemporarilyNotMetered */, false /* isRoaming */);
}
+ private static NetworkStateSnapshot buildSatelliteMobileState(String subscriberId) {
+ return buildStateOfTransport(NetworkCapabilities.TRANSPORT_SATELLITE, TYPE_MOBILE,
+ TEST_IFACE, subscriberId, null /* wifiNetworkKey */,
+ false /* isTemporarilyNotMetered */, false /* isRoaming */);
+ }
+
private static NetworkStateSnapshot buildTestState(@NonNull String iface,
@Nullable String wifiNetworkKey) {
return buildStateOfTransport(NetworkCapabilities.TRANSPORT_TEST, TYPE_TEST,
diff --git a/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt b/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt
index 27e6f96..99f762d 100644
--- a/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt
+++ b/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt
@@ -16,30 +16,35 @@
package com.android.server.net
-import android.net.NetworkStats
+import android.net.NetworkStats.Entry
import com.android.testutils.DevSdkIgnoreRunner
import java.time.Clock
+import java.util.function.Supplier
import kotlin.test.assertEquals
import kotlin.test.assertNull
+import kotlin.test.fail
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
@RunWith(DevSdkIgnoreRunner::class)
class TrafficStatsRateLimitCacheTest {
companion object {
private const val expiryDurationMs = 1000L
+ private const val maxSize = 2
}
private val clock = mock(Clock::class.java)
- private val entry = mock(NetworkStats.Entry::class.java)
- private val cache = TrafficStatsRateLimitCache(clock, expiryDurationMs)
+ private val entry = mock(Entry::class.java)
+ private val cache = TrafficStatsRateLimitCache(clock, expiryDurationMs, maxSize)
@Test
fun testGet_returnsEntryIfNotExpired() {
cache.put("iface", 2, entry)
- `when`(clock.millis()).thenReturn(500L) // Set clock to before expiry
+ doReturn(500L).`when`(clock).millis() // Set clock to before expiry
val result = cache.get("iface", 2)
assertEquals(entry, result)
}
@@ -47,7 +52,7 @@
@Test
fun testGet_returnsNullIfExpired() {
cache.put("iface", 2, entry)
- `when`(clock.millis()).thenReturn(2000L) // Set clock to after expiry
+ doReturn(2000L).`when`(clock).millis() // Set clock to after expiry
assertNull(cache.get("iface", 2))
}
@@ -59,8 +64,8 @@
@Test
fun testPutAndGet_retrievesCorrectEntryForDifferentKeys() {
- val entry1 = mock(NetworkStats.Entry::class.java)
- val entry2 = mock(NetworkStats.Entry::class.java)
+ val entry1 = mock(Entry::class.java)
+ val entry2 = mock(Entry::class.java)
cache.put("iface1", 2, entry1)
cache.put("iface2", 4, entry2)
@@ -71,8 +76,8 @@
@Test
fun testPut_overridesExistingEntry() {
- val entry1 = mock(NetworkStats.Entry::class.java)
- val entry2 = mock(NetworkStats.Entry::class.java)
+ val entry1 = mock(Entry::class.java)
+ val entry2 = mock(Entry::class.java)
cache.put("iface", 2, entry1)
cache.put("iface", 2, entry2) // Put with the same key
@@ -81,6 +86,62 @@
}
@Test
+ fun testPut_removeLru() {
+ // Assumes max size is 2. Verify eldest entry get removed.
+ val entry1 = mock(Entry::class.java)
+ val entry2 = mock(Entry::class.java)
+ val entry3 = mock(Entry::class.java)
+
+ cache.put("iface1", 2, entry1)
+ cache.put("iface2", 4, entry2)
+ cache.put("iface3", 8, entry3)
+
+ assertNull(cache.get("iface1", 2))
+ assertEquals(entry2, cache.get("iface2", 4))
+ assertEquals(entry3, cache.get("iface3", 8))
+ }
+
+ @Test
+ fun testGetOrCompute_cacheHit() {
+ val entry1 = mock(Entry::class.java)
+
+ cache.put("iface1", 2, entry1)
+
+ // Set clock to before expiry.
+ doReturn(500L).`when`(clock).millis()
+
+ // Now call getOrCompute
+ val result = cache.getOrCompute("iface1", 2) {
+ fail("Supplier should not be called")
+ }
+
+ // Assertions
+ assertEquals(entry1, result) // Should get the cached entry.
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun testGetOrCompute_cacheMiss() {
+ val entry1 = mock(Entry::class.java)
+
+ cache.put("iface1", 2, entry1)
+
+ // Set clock to after expiry.
+ doReturn(1500L).`when`(clock).millis()
+
+ // Mock the supplier to return our network stats entry.
+ val supplier = mock(Supplier::class.java) as Supplier<Entry>
+ doReturn(entry1).`when`(supplier).get()
+
+ // Now call getOrCompute.
+ val result = cache.getOrCompute("iface1", 2, supplier)
+
+ // Assertions.
+ assertEquals(entry1, result) // Should get the cached entry.
+ verify(supplier).get()
+ }
+
+ @Test
fun testClear() {
cache.put("iface", 2, entry)
cache.clear()
diff --git a/tests/unit/vpn-jarjar-rules.txt b/tests/unit/vpn-jarjar-rules.txt
deleted file mode 100644
index f74eab8..0000000
--- a/tests/unit/vpn-jarjar-rules.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-# Only keep classes imported by ConnectivityServiceTest
-keep com.android.server.connectivity.VpnProfileStore
diff --git a/thread/README.md b/thread/README.md
index f50e0cd..41b73ac 100644
--- a/thread/README.md
+++ b/thread/README.md
@@ -1,3 +1,18 @@
# Thread
Bring the [Thread](https://www.threadgroup.org/) networking protocol to Android.
+
+## Try Thread with Cuttlefish
+
+```
+# Get the code and go to the Android source code root directory
+
+source build/envsetup.sh
+lunch aosp_cf_x86_64_phone-trunk_staging-userdebug
+m
+
+launch_cvd
+```
+
+Open `https://localhost:8443/` in your web browser, you can find the Thread
+demoapp (with the Thread logo) in the cuttlefish instance. Open it and have fun with Thread!
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/apex/ot-daemon.34rc b/thread/apex/ot-daemon.34rc
index 25060d1..86f6b69 100644
--- a/thread/apex/ot-daemon.34rc
+++ b/thread/apex/ot-daemon.34rc
@@ -21,5 +21,5 @@
user thread_network
group thread_network inet system
seclabel u:r:ot_daemon:s0
- socket ot-daemon/thread-wpan.sock stream 0666 thread_network thread_network
+ socket ot-daemon/thread-wpan.sock stream 0660 thread_network thread_network
override
diff --git a/thread/demoapp/Android.bp b/thread/demoapp/Android.bp
index fcfd469..117b4f9 100644
--- a/thread/demoapp/Android.bp
+++ b/thread/demoapp/Android.bp
@@ -34,7 +34,19 @@
libs: [
"framework-connectivity-t",
],
+ required: [
+ "privapp-permissions-com.android.threadnetwork.demoapp",
+ ],
+ system_ext_specific: true,
certificate: "platform",
privileged: true,
platform_apis: true,
}
+
+prebuilt_etc {
+ name: "privapp-permissions-com.android.threadnetwork.demoapp",
+ src: "privapp-permissions-com.android.threadnetwork.demoapp.xml",
+ sub_dir: "permissions",
+ filename_from_src: true,
+ system_ext_specific: true,
+}
diff --git a/thread/demoapp/privapp-permissions-com.android.threadnetwork.demoapp.xml b/thread/demoapp/privapp-permissions-com.android.threadnetwork.demoapp.xml
new file mode 100644
index 0000000..1995e60
--- /dev/null
+++ b/thread/demoapp/privapp-permissions-com.android.threadnetwork.demoapp.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<!-- The privileged permissions needed by the com.android.threadnetwork.demoapp app. -->
+<permissions>
+ <privapp-permissions package="com.android.threadnetwork.demoapp">
+ <permission name="android.permission.THREAD_NETWORK_PRIVILEGED" />
+ </privapp-permissions>
+</permissions>
diff --git a/thread/framework/Android.bp b/thread/framework/Android.bp
index 846253c..f8fe422 100644
--- a/thread/framework/Android.bp
+++ b/thread/framework/Android.bp
@@ -30,3 +30,14 @@
"//packages/modules/Connectivity:__subpackages__",
],
}
+
+filegroup {
+ name: "framework-thread-ot-daemon-shared-aidl-sources",
+ srcs: [
+ "java/android/net/thread/ChannelMaxPower.aidl",
+ ],
+ path: "java",
+ visibility: [
+ "//external/ot-br-posix:__subpackages__",
+ ],
+}
diff --git a/thread/framework/java/android/net/thread/ActiveOperationalDataset.java b/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
index b74a15a..22457f5 100644
--- a/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
+++ b/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
@@ -18,7 +18,7 @@
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkState;
-import static com.android.net.module.util.HexDump.dumpHexString;
+import static com.android.net.module.util.HexDump.toHexString;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
@@ -74,42 +74,61 @@
public final class ActiveOperationalDataset implements Parcelable {
/** The maximum length of the Active Operational Dataset TLV array in bytes. */
public static final int LENGTH_MAX_DATASET_TLVS = 254;
+
/** The length of Extended PAN ID in bytes. */
public static final int LENGTH_EXTENDED_PAN_ID = 8;
+
/** The minimum length of Network Name as UTF-8 bytes. */
public static final int LENGTH_MIN_NETWORK_NAME_BYTES = 1;
+
/** The maximum length of Network Name as UTF-8 bytes. */
public static final int LENGTH_MAX_NETWORK_NAME_BYTES = 16;
+
/** The length of Network Key in bytes. */
public static final int LENGTH_NETWORK_KEY = 16;
+
/** The length of Mesh-Local Prefix in bits. */
public static final int LENGTH_MESH_LOCAL_PREFIX_BITS = 64;
+
/** The length of PSKc in bytes. */
public static final int LENGTH_PSKC = 16;
+
/** The 2.4 GHz channel page. */
public static final int CHANNEL_PAGE_24_GHZ = 0;
+
/** The minimum 2.4GHz channel. */
public static final int CHANNEL_MIN_24_GHZ = 11;
+
/** The maximum 2.4GHz channel. */
public static final int CHANNEL_MAX_24_GHZ = 26;
+
/** @hide */
@VisibleForTesting public static final int TYPE_CHANNEL = 0;
+
/** @hide */
@VisibleForTesting public static final int TYPE_PAN_ID = 1;
+
/** @hide */
@VisibleForTesting public static final int TYPE_EXTENDED_PAN_ID = 2;
+
/** @hide */
@VisibleForTesting public static final int TYPE_NETWORK_NAME = 3;
+
/** @hide */
@VisibleForTesting public static final int TYPE_PSKC = 4;
+
/** @hide */
@VisibleForTesting public static final int TYPE_NETWORK_KEY = 5;
+
/** @hide */
@VisibleForTesting public static final int TYPE_MESH_LOCAL_PREFIX = 7;
+
/** @hide */
@VisibleForTesting public static final int TYPE_SECURITY_POLICY = 12;
+
/** @hide */
@VisibleForTesting public static final int TYPE_ACTIVE_TIMESTAMP = 14;
+
/** @hide */
@VisibleForTesting public static final int TYPE_CHANNEL_MASK = 53;
@@ -591,7 +610,7 @@
sb.append("{networkName=")
.append(getNetworkName())
.append(", extendedPanId=")
- .append(dumpHexString(getExtendedPanId()))
+ .append(toHexString(getExtendedPanId()))
.append(", panId=")
.append(getPanId())
.append(", channel=")
@@ -975,8 +994,10 @@
public static final class SecurityPolicy {
/** The default Rotation Time in hours. */
public static final int DEFAULT_ROTATION_TIME_HOURS = 672;
+
/** The minimum length of Security Policy flags in bytes. */
public static final int LENGTH_MIN_SECURITY_POLICY_FLAGS = 1;
+
/** The length of Rotation Time TLV value in bytes. */
private static final int LENGTH_SECURITY_POLICY_ROTATION_TIME = 2;
@@ -1088,7 +1109,7 @@
sb.append("{rotation=")
.append(mRotationTimeHours)
.append(", flags=")
- .append(dumpHexString(mFlags))
+ .append(toHexString(mFlags))
.append("}");
return sb.toString();
}
diff --git a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java b/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
similarity index 66%
copy from tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
copy to thread/framework/java/android/net/thread/ChannelMaxPower.aidl
index 472e347..bcda8a8 100644
--- a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
+++ b/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
@@ -14,8 +14,15 @@
* limitations under the License.
*/
-package com.android.cts.net.arguments;
+package android.net.thread;
-public interface InstrumentationArguments {
- String ARG_WAIVE_BIND_PRIORITY = "waive_bind_priority";
+ /**
+ * Mapping from a channel to its max power.
+ *
+ * {@hide}
+ */
+parcelable ChannelMaxPower {
+ int channel; // The Thread radio channel.
+ int maxPower; // The max power in the unit of 0.01dBm. Passing INT16_MAX(32767) will
+ // disable the channel.
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java b/thread/framework/java/android/net/thread/IConfigurationReceiver.aidl
similarity index 64%
copy from tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java
copy to thread/framework/java/android/net/thread/IConfigurationReceiver.aidl
index 6596269..dcc4545 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java
+++ b/thread/framework/java/android/net/thread/IConfigurationReceiver.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 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.
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package android.net.thread;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import android.net.thread.ThreadConfiguration;
-@RequiredProperties({NON_METERED_NETWORK})
-public class ExpeditedJobNonMeteredTest extends AbstractExpeditedJobTest {
+/** Receives the result of a Thread Configuration change. @hide */
+oneway interface IConfigurationReceiver {
+ void onConfigurationChanged(in ThreadConfiguration configuration);
}
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index 485e25d..f50de74 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -17,12 +17,15 @@
package android.net.thread;
import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.ChannelMaxPower;
import android.net.thread.IActiveOperationalDatasetReceiver;
-import android.net.thread.IOperationalDatasetCallback;
+import android.net.thread.IConfigurationReceiver;
import android.net.thread.IOperationReceiver;
+import android.net.thread.IOperationalDatasetCallback;
import android.net.thread.IScheduleMigrationReceiver;
import android.net.thread.IStateCallback;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
/**
* Interface for communicating with ThreadNetworkControllerService.
@@ -39,9 +42,13 @@
void leave(in IOperationReceiver receiver);
void setTestNetworkAsUpstream(in String testNetworkInterfaceName, in IOperationReceiver receiver);
+ void setChannelMaxPowers(in ChannelMaxPower[] channelMaxPowers, in IOperationReceiver receiver);
int getThreadVersion();
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
void setEnabled(boolean enabled, in IOperationReceiver receiver);
+ void setConfiguration(in ThreadConfiguration config, in IOperationReceiver receiver);
+ void registerConfigurationCallback(in IConfigurationReceiver receiver);
+ void unregisterConfigurationCallback(in IConfigurationReceiver receiver);
}
diff --git a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
index 520acbd..cecb4e9 100644
--- a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
+++ b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
@@ -65,11 +65,32 @@
*/
@NonNull
public static OperationalDatasetTimestamp fromInstant(@NonNull Instant instant) {
+ return OperationalDatasetTimestamp.fromInstant(instant, true /* isAuthoritativeSource */);
+ }
+
+ /**
+ * Creates a new {@link OperationalDatasetTimestamp} object from an {@link Instant}.
+ *
+ * <p>The {@code seconds} is set to {@code instant.getEpochSecond()}, {@code ticks} is set to
+ * {@link instant#getNano()} based on frequency of 32768 Hz, and {@code isAuthoritativeSource}
+ * is set to {@code isAuthoritativeSource}.
+ *
+ * <p>Note that this conversion can lose precision and a value returned by {@link #toInstant}
+ * may not equal exactly the {@code instant}.
+ *
+ * @throws IllegalArgumentException if {@code instant.getEpochSecond()} is larger than {@code
+ * 0xffffffffffffL}
+ * @see toInstant
+ * @hide
+ */
+ @NonNull
+ public static OperationalDatasetTimestamp fromInstant(
+ @NonNull Instant instant, boolean isAuthoritativeSource) {
int ticks = getRoundedTicks(instant.getNano());
long seconds = instant.getEpochSecond() + ticks / TICKS_UPPER_BOUND;
// the rounded ticks can be 0x8000 if instant.getNano() >= 999984742
ticks = ticks % TICKS_UPPER_BOUND;
- return new OperationalDatasetTimestamp(seconds, ticks, true /* isAuthoritativeSource */);
+ return new OperationalDatasetTimestamp(seconds, ticks, isAuthoritativeSource);
}
/**
diff --git a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java b/thread/framework/java/android/net/thread/ThreadConfiguration.aidl
similarity index 73%
copy from tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
copy to thread/framework/java/android/net/thread/ThreadConfiguration.aidl
index 472e347..9473411 100644
--- a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
+++ b/thread/framework/java/android/net/thread/ThreadConfiguration.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright 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.
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-package com.android.cts.net.arguments;
+package android.net.thread;
-public interface InstrumentationArguments {
- String ARG_WAIVE_BIND_PRIORITY = "waive_bind_priority";
-}
+parcelable ThreadConfiguration;
diff --git a/thread/framework/java/android/net/thread/ThreadConfiguration.java b/thread/framework/java/android/net/thread/ThreadConfiguration.java
new file mode 100644
index 0000000..e09b3a6
--- /dev/null
+++ b/thread/framework/java/android/net/thread/ThreadConfiguration.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 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 android.net.thread;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Data interface for Thread device configuration.
+ *
+ * <p>An example usage of creating a {@link ThreadConfiguration} that turns on NAT64 feature based
+ * on an existing {@link ThreadConfiguration}:
+ *
+ * <pre>{@code
+ * ThreadConfiguration config =
+ * new ThreadConfiguration.Builder(existingConfig).setNat64Enabled(true).build();
+ * }</pre>
+ *
+ * @see ThreadNetworkController#setConfiguration
+ * @see ThreadNetworkController#registerConfigurationCallback
+ * @see ThreadNetworkController#unregisterConfigurationCallback
+ * @hide
+ */
+// @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+// @SystemApi
+public final class ThreadConfiguration implements Parcelable {
+ private final boolean mNat64Enabled;
+ private final boolean mDhcp6PdEnabled;
+
+ private ThreadConfiguration(Builder builder) {
+ this(builder.mNat64Enabled, builder.mDhcp6PdEnabled);
+ }
+
+ private ThreadConfiguration(boolean nat64Enabled, boolean dhcp6PdEnabled) {
+ this.mNat64Enabled = nat64Enabled;
+ this.mDhcp6PdEnabled = dhcp6PdEnabled;
+ }
+
+ /** Returns {@code true} if NAT64 is enabled. */
+ public boolean isNat64Enabled() {
+ return mNat64Enabled;
+ }
+
+ /** Returns {@code true} if DHCPv6 Prefix Delegation is enabled. */
+ public boolean isDhcp6PdEnabled() {
+ return mDhcp6PdEnabled;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof ThreadConfiguration)) {
+ return false;
+ } else {
+ ThreadConfiguration otherConfig = (ThreadConfiguration) other;
+ return mNat64Enabled == otherConfig.mNat64Enabled
+ && mDhcp6PdEnabled == otherConfig.mDhcp6PdEnabled;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNat64Enabled, mDhcp6PdEnabled);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ sb.append("Nat64Enabled=").append(mNat64Enabled);
+ sb.append(", Dhcp6PdEnabled=").append(mDhcp6PdEnabled);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mNat64Enabled);
+ dest.writeBoolean(mDhcp6PdEnabled);
+ }
+
+ public static final @NonNull Creator<ThreadConfiguration> CREATOR =
+ new Creator<>() {
+ @Override
+ public ThreadConfiguration createFromParcel(Parcel in) {
+ ThreadConfiguration.Builder builder = new ThreadConfiguration.Builder();
+ builder.setNat64Enabled(in.readBoolean());
+ builder.setDhcp6PdEnabled(in.readBoolean());
+ return builder.build();
+ }
+
+ @Override
+ public ThreadConfiguration[] newArray(int size) {
+ return new ThreadConfiguration[size];
+ }
+ };
+
+ /** The builder for creating {@link ThreadConfiguration} objects. */
+ public static final class Builder {
+ private boolean mNat64Enabled = false;
+ private boolean mDhcp6PdEnabled = false;
+
+ /** Creates a new {@link Builder} object with all features disabled. */
+ public Builder() {}
+
+ /**
+ * Creates a new {@link Builder} object from a {@link ThreadConfiguration} object.
+ *
+ * @param config the Border Router configurations to be copied
+ */
+ public Builder(@NonNull ThreadConfiguration config) {
+ Objects.requireNonNull(config);
+
+ mNat64Enabled = config.mNat64Enabled;
+ mDhcp6PdEnabled = config.mDhcp6PdEnabled;
+ }
+
+ /**
+ * Enables or disables NAT64 for the device.
+ *
+ * <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
+ * IPv4.
+ */
+ @NonNull
+ public Builder setNat64Enabled(boolean enabled) {
+ this.mNat64Enabled = enabled;
+ return this;
+ }
+
+ /**
+ * Enables or disables Prefix Delegation for the device.
+ *
+ * <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
+ * IPv6.
+ */
+ @NonNull
+ public Builder setDhcp6PdEnabled(boolean enabled) {
+ this.mDhcp6PdEnabled = enabled;
+ return this;
+ }
+
+ /** Creates a new {@link ThreadConfiguration} object. */
+ @NonNull
+ public ThreadConfiguration build() {
+ return new ThreadConfiguration(this);
+ }
+ }
+}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index db761a3..30b3d6a 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -25,10 +25,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.Size;
import android.annotation.SystemApi;
import android.os.Binder;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
+import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -39,6 +41,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Provides the primary APIs for controlling all aspects of a Thread network.
@@ -98,6 +101,12 @@
/** Thread standard version 1.3. */
public static final int THREAD_VERSION_1_3 = 4;
+ /** Minimum value of max power in unit of 0.01dBm. @hide */
+ private static final int POWER_LIMITATION_MIN = -32768;
+
+ /** Maximum value of max power in unit of 0.01dBm. @hide */
+ private static final int POWER_LIMITATION_MAX = 32767;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({THREAD_VERSION_1_3})
@@ -116,6 +125,12 @@
private final Map<OperationalDatasetCallback, OperationalDatasetCallbackProxy>
mOpDatasetCallbackMap = new HashMap<>();
+ private final Object mConfigurationCallbackMapLock = new Object();
+
+ @GuardedBy("mConfigurationCallbackMapLock")
+ private final Map<Consumer<ThreadConfiguration>, ConfigurationCallbackProxy>
+ mConfigurationCallbackMap = new HashMap<>();
+
/** @hide */
public ThreadNetworkController(@NonNull IThreadNetworkController controllerService) {
requireNonNull(controllerService, "controllerService cannot be null");
@@ -571,6 +586,97 @@
}
/**
+ * Configures the Thread features for this device.
+ *
+ * <p>This method sets the {@link ThreadConfiguration} for this device. On success, the {@link
+ * OutcomeReceiver#onResult} will be called, and the {@code configuration} will be applied and
+ * persisted to the device; the configuration changes can be observed by {@link
+ * #registerConfigurationCallback}. On failure, {@link OutcomeReceiver#onError} of {@code
+ * receiver} will be invoked with a specific error.
+ *
+ * @param configuration the configuration to set
+ * @param executor the executor to execute {@code receiver}
+ * @param receiver the receiver to receive result of this operation
+ * @hide
+ */
+ // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ public void setConfiguration(
+ @NonNull ThreadConfiguration configuration,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ requireNonNull(configuration, "Configuration cannot be null");
+ requireNonNull(executor, "executor cannot be null");
+ requireNonNull(receiver, "receiver cannot be null");
+ try {
+ mControllerService.setConfiguration(
+ configuration, new OperationReceiverProxy(executor, receiver));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a callback to be called when the configuration is changed.
+ *
+ * <p>Upon return of this method, {@code callback} will be invoked immediately with the new
+ * {@link ThreadConfiguration}.
+ *
+ * @param executor the executor to execute the {@code callback}
+ * @param callback the callback to receive Thread configuration changes
+ * @throws IllegalArgumentException if {@code callback} has already been registered
+ * @hide
+ */
+ // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ public void registerConfigurationCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<ThreadConfiguration> callback) {
+ requireNonNull(executor, "executor cannot be null");
+ requireNonNull(callback, "callback cannot be null");
+ synchronized (mConfigurationCallbackMapLock) {
+ if (mConfigurationCallbackMap.containsKey(callback)) {
+ throw new IllegalArgumentException("callback has already been registered");
+ }
+ ConfigurationCallbackProxy callbackProxy =
+ new ConfigurationCallbackProxy(executor, callback);
+ mConfigurationCallbackMap.put(callback, callbackProxy);
+ try {
+ mControllerService.registerConfigurationCallback(callbackProxy);
+ } catch (RemoteException e) {
+ mConfigurationCallbackMap.remove(callback);
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregisters the configuration callback.
+ *
+ * @param callback the callback which has been registered with {@link
+ * #registerConfigurationCallback}
+ * @throws IllegalArgumentException if {@code callback} hasn't been registered
+ * @hide
+ */
+ // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ public void unregisterConfigurationCallback(@NonNull Consumer<ThreadConfiguration> callback) {
+ requireNonNull(callback, "callback cannot be null");
+ synchronized (mConfigurationCallbackMapLock) {
+ ConfigurationCallbackProxy callbackProxy = mConfigurationCallbackMap.get(callback);
+ if (callbackProxy == null) {
+ throw new IllegalArgumentException("callback hasn't been registered");
+ }
+ try {
+ mControllerService.unregisterConfigurationCallback(callbackProxy);
+ mConfigurationCallbackMap.remove(callbackProxy.mConfigurationConsumer);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Sets to use a specified test network as the upstream.
*
* @param testNetworkInterfaceName The name of the test network interface. When it's null,
@@ -596,6 +702,98 @@
}
}
+ /**
+ * Sets max power of each channel.
+ *
+ * <p>If not set, the default max power is set by the Thread HAL service or the Thread radio
+ * chip firmware.
+ *
+ * <p>On success, the Pending Dataset is successfully registered and persisted on the Leader and
+ * {@link OutcomeReceiver#onResult} of {@code receiver} will be called; When failed, {@link
+ * OutcomeReceiver#onError} will be called with a specific error:
+ *
+ * <ul>
+ * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_OPERATION} the operation is no
+ * supported by the platform.
+ * </ul>
+ *
+ * @param channelMaxPowers SparseIntArray (key: channel, value: max power) consists of channel
+ * and corresponding max power. Valid channel values should be between {@link
+ * ActiveOperationalDataset#CHANNEL_MIN_24_GHZ} and {@link
+ * ActiveOperationalDataset#CHANNEL_MAX_24_GHZ}. The unit of the max power is 0.01dBm. Max
+ * power values should be between INT16_MIN (-32768) and INT16_MAX (32767). If the max power
+ * is set to INT16_MAX, the corresponding channel is not supported.
+ * @param executor the executor to execute {@code receiver}.
+ * @param receiver the receiver to receive the result of this operation.
+ * @throws IllegalArgumentException if the size of {@code channelMaxPowers} is smaller than 1,
+ * or invalid channel or max power is configured.
+ * @hide
+ */
+ @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
+ public final void setChannelMaxPowers(
+ @NonNull @Size(min = 1) SparseIntArray channelMaxPowers,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ requireNonNull(channelMaxPowers, "channelMaxPowers cannot be null");
+ requireNonNull(executor, "executor cannot be null");
+ requireNonNull(receiver, "receiver cannot be null");
+
+ if (channelMaxPowers.size() < 1) {
+ throw new IllegalArgumentException("channelMaxPowers cannot be empty");
+ }
+
+ for (int i = 0; i < channelMaxPowers.size(); i++) {
+ int channel = channelMaxPowers.keyAt(i);
+ int maxPower = channelMaxPowers.get(channel);
+
+ if ((channel < ActiveOperationalDataset.CHANNEL_MIN_24_GHZ)
+ || (channel > ActiveOperationalDataset.CHANNEL_MAX_24_GHZ)) {
+ throw new IllegalArgumentException(
+ "Channel "
+ + channel
+ + " exceeds allowed range ["
+ + ActiveOperationalDataset.CHANNEL_MIN_24_GHZ
+ + ", "
+ + ActiveOperationalDataset.CHANNEL_MAX_24_GHZ
+ + "]");
+ }
+
+ if ((maxPower < POWER_LIMITATION_MIN) || (maxPower > POWER_LIMITATION_MAX)) {
+ throw new IllegalArgumentException(
+ "Channel power ({channel: "
+ + channel
+ + ", maxPower: "
+ + maxPower
+ + "}) exceeds allowed range ["
+ + POWER_LIMITATION_MIN
+ + ", "
+ + POWER_LIMITATION_MAX
+ + "]");
+ }
+ }
+
+ try {
+ mControllerService.setChannelMaxPowers(
+ toChannelMaxPowerArray(channelMaxPowers),
+ new OperationReceiverProxy(executor, receiver));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static ChannelMaxPower[] toChannelMaxPowerArray(
+ @NonNull SparseIntArray channelMaxPowers) {
+ final ChannelMaxPower[] powerArray = new ChannelMaxPower[channelMaxPowers.size()];
+
+ for (int i = 0; i < channelMaxPowers.size(); i++) {
+ powerArray[i] = new ChannelMaxPower();
+ powerArray[i].channel = channelMaxPowers.keyAt(i);
+ powerArray[i].maxPower = channelMaxPowers.get(powerArray[i].channel);
+ }
+
+ return powerArray;
+ }
+
private static <T> void propagateError(
Executor executor,
OutcomeReceiver<T, ThreadNetworkException> receiver,
@@ -664,4 +862,26 @@
propagateError(mExecutor, mResultReceiver, errorCode, errorMessage);
}
}
+
+ private static final class ConfigurationCallbackProxy extends IConfigurationReceiver.Stub {
+ final Executor mExecutor;
+ final Consumer<ThreadConfiguration> mConfigurationConsumer;
+
+ ConfigurationCallbackProxy(
+ @CallbackExecutor Executor executor,
+ Consumer<ThreadConfiguration> ConfigurationConsumer) {
+ this.mExecutor = executor;
+ this.mConfigurationConsumer = ConfigurationConsumer;
+ }
+
+ @Override
+ public void onConfigurationChanged(ThreadConfiguration configuration) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mConfigurationConsumer.accept(configuration));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkException.java b/thread/framework/java/android/net/thread/ThreadNetworkException.java
index 4def0fb..f699c30 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkException.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkException.java
@@ -138,8 +138,17 @@
*/
public static final int ERROR_THREAD_DISABLED = 12;
+ /**
+ * The operation failed because it is not supported by the platform. For example, some platforms
+ * may not support setting the target power of each channel. The caller should not retry and may
+ * return an error to the user.
+ *
+ * @hide
+ */
+ public static final int ERROR_UNSUPPORTED_OPERATION = 13;
+
private static final int ERROR_MIN = ERROR_INTERNAL_ERROR;
- private static final int ERROR_MAX = ERROR_THREAD_DISABLED;
+ private static final int ERROR_MAX = ERROR_UNSUPPORTED_OPERATION;
private final int mErrorCode;
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkFlags.java b/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
index e6ab988..691bbf5 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
@@ -27,5 +27,9 @@
/** @hide */
public static final String FLAG_THREAD_ENABLED = "com.android.net.thread.flags.thread_enabled";
+ /** @hide */
+ public static final String FLAG_CONFIGURATION_ENABLED =
+ "com.android.net.thread.flags.configuration_enabled";
+
private ThreadNetworkFlags() {}
}
diff --git a/thread/service/Android.bp b/thread/service/Android.bp
index 6e2fac1..a82a499 100644
--- a/thread/service/Android.bp
+++ b/thread/service/Android.bp
@@ -45,6 +45,9 @@
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-netlink",
+ // The required dependency net-utils-device-common-struct-base is in the classpath via
+ // framework-connectivity
+ "net-utils-device-common-struct",
"ot-daemon-aidl-java",
],
apex_available: ["com.android.tethering"],
diff --git a/thread/service/java/com/android/server/thread/ActiveOperationalDatasetReceiverWrapper.java b/thread/service/java/com/android/server/thread/ActiveOperationalDatasetReceiverWrapper.java
index e3b4e1a..43ff336 100644
--- a/thread/service/java/com/android/server/thread/ActiveOperationalDatasetReceiverWrapper.java
+++ b/thread/service/java/com/android/server/thread/ActiveOperationalDatasetReceiverWrapper.java
@@ -16,10 +16,12 @@
package com.android.server.thread;
+import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IActiveOperationalDatasetReceiver;
+import android.net.thread.ThreadNetworkException;
import android.os.RemoteException;
import com.android.internal.annotations.GuardedBy;
@@ -73,6 +75,17 @@
}
}
+ public void onError(Throwable e) {
+ if (e instanceof ThreadNetworkException) {
+ ThreadNetworkException threadException = (ThreadNetworkException) e;
+ onError(threadException.getErrorCode(), threadException.getMessage());
+ } else if (e instanceof RemoteException) {
+ onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ } else {
+ throw new AssertionError(e);
+ }
+ }
+
public void onError(int errorCode, String errorMessage) {
synchronized (sPendingReceiversLock) {
sPendingReceivers.remove(this);
diff --git a/thread/service/java/com/android/server/thread/InfraInterfaceController.java b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
index be54cbc..e72c9ee 100644
--- a/thread/service/java/com/android/server/thread/InfraInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
@@ -16,14 +16,30 @@
package com.android.server.thread;
-import android.os.ParcelFileDescriptor;
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_RAW;
+import static android.system.OsConstants.IPV6_CHECKSUM;
+import static android.system.OsConstants.IPV6_MULTICAST_HOPS;
+import static android.system.OsConstants.IPV6_RECVHOPLIMIT;
+import static android.system.OsConstants.IPV6_RECVPKTINFO;
+import static android.system.OsConstants.IPV6_UNICAST_HOPS;
+import android.net.util.SocketUtils;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.FileDescriptor;
import java.io.IOException;
/** Controller for the infrastructure network interface. */
public class InfraInterfaceController {
private static final String TAG = "InfraIfController";
+ private static final int ENABLE = 1;
+ private static final int IPV6_CHECKSUM_OFFSET = 2;
+ private static final int HOP_LIMIT = 255;
+
static {
System.loadLibrary("service-thread-jni");
}
@@ -37,8 +53,21 @@
* @throws IOException when fails to create the socket.
*/
public ParcelFileDescriptor createIcmp6Socket(String infraInterfaceName) throws IOException {
- return ParcelFileDescriptor.adoptFd(nativeCreateIcmp6Socket(infraInterfaceName));
+ ParcelFileDescriptor parcelFd =
+ ParcelFileDescriptor.adoptFd(nativeCreateFilteredIcmp6Socket());
+ FileDescriptor fd = parcelFd.getFileDescriptor();
+ try {
+ Os.setsockoptInt(fd, IPPROTO_RAW, IPV6_CHECKSUM, IPV6_CHECKSUM_OFFSET);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, ENABLE);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, ENABLE);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, HOP_LIMIT);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, HOP_LIMIT);
+ SocketUtils.bindSocketToInterface(fd, infraInterfaceName);
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to setsockopt for the ICMPv6 socket", e);
+ }
+ return parcelFd;
}
- private static native int nativeCreateIcmp6Socket(String interfaceName) throws IOException;
+ private static native int nativeCreateFilteredIcmp6Socket() throws IOException;
}
diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java
index c74c023..1447ff8 100644
--- a/thread/service/java/com/android/server/thread/NsdPublisher.java
+++ b/thread/service/java/com/android/server/thread/NsdPublisher.java
@@ -19,9 +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;
@@ -30,14 +36,19 @@
import com.android.internal.annotations.VisibleForTesting;
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;
-import java.util.ArrayDeque;
+import java.net.Inet6Address;
+import java.net.InetAddress;
import java.util.ArrayList;
-import java.util.Deque;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
/**
@@ -48,33 +59,38 @@
*
* <p>All the data members of this class MUST be accessed in the {@code mHandler}'s Thread except
* {@code mHandler} itself.
- *
- * <p>TODO: b/323300118 - Remove the following mechanism when the race condition in NsdManager is
- * fixed.
- *
- * <p>There's always only one running registration job at any timepoint. All other pending jobs are
- * queued in {@code mRegistrationJobs}. When a registration job is complete (i.e. the according
- * method in {@link NsdManager.RegistrationListener} is called), it will start the next registration
- * job in the queue.
*/
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 Deque<Runnable> mRegistrationJobs = new ArrayDeque<>();
+ 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
@@ -87,13 +103,9 @@
List<DnsTxtAttribute> txt,
INsdStatusReceiver receiver,
int listenerId) {
- postRegistrationJob(
- () -> {
- NsdServiceInfo serviceInfo =
- buildServiceInfoForService(
- hostname, name, type, subTypeList, port, txt);
- registerInternal(serviceInfo, receiver, listenerId, "service");
- });
+ NsdServiceInfo serviceInfo =
+ buildServiceInfoForService(hostname, name, type, subTypeList, port, txt);
+ mHandler.post(() -> registerInternal(serviceInfo, receiver, listenerId, "service"));
}
private static NsdServiceInfo buildServiceInfoForService(
@@ -119,6 +131,27 @@
return serviceInfo;
}
+ @Override
+ public void registerHost(
+ String name, List<String> addresses, INsdStatusReceiver receiver, int listenerId) {
+ NsdServiceInfo serviceInfo = buildServiceInfoForHost(name, addresses);
+ mHandler.post(() -> registerInternal(serviceInfo, receiver, listenerId, "host"));
+ }
+
+ private static NsdServiceInfo buildServiceInfoForHost(
+ String name, List<String> addressStrings) {
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+
+ serviceInfo.setHostname(name);
+ ArrayList<InetAddress> addresses = new ArrayList<>(addressStrings.size());
+ for (String addressString : addressStrings) {
+ addresses.add(InetAddresses.parseNumericAddress(addressString));
+ }
+ serviceInfo.setHostAddresses(addresses);
+
+ return serviceInfo;
+ }
+
private void registerInternal(
NsdServiceInfo serviceInfo,
INsdStatusReceiver receiver,
@@ -144,7 +177,7 @@
}
public void unregister(INsdStatusReceiver receiver, int listenerId) {
- postRegistrationJob(() -> unregisterInternal(receiver, listenerId));
+ mHandler.post(() -> unregisterInternal(receiver, listenerId));
}
public void unregisterInternal(INsdStatusReceiver receiver, int listenerId) {
@@ -171,6 +204,157 @@
mNsdManager.unregisterService(registrationListener);
}
+ @Override
+ public void discoverService(String type, INsdDiscoverServiceCallback callback, int listenerId) {
+ mHandler.post(() -> discoverServiceInternal(type, callback, listenerId));
+ }
+
+ private void discoverServiceInternal(
+ String type, INsdDiscoverServiceCallback callback, int listenerId) {
+ checkOnHandlerThread();
+ Log.i(
+ TAG,
+ "Discovering services."
+ + " Listener ID: "
+ + listenerId
+ + ", service type: "
+ + type);
+
+ DiscoveryListener listener = new DiscoveryListener(listenerId, type, callback);
+ mDiscoveryListeners.append(listenerId, listener);
+ DiscoveryRequest discoveryRequest =
+ new DiscoveryRequest.Builder(type).setNetwork(null).build();
+ mNsdManager.discoverServices(discoveryRequest, mExecutor, listener);
+ }
+
+ @Override
+ public void stopServiceDiscovery(int listenerId) {
+ mHandler.post(() -> stopServiceDiscoveryInternal(listenerId));
+ }
+
+ private void stopServiceDiscoveryInternal(int listenerId) {
+ checkOnHandlerThread();
+
+ DiscoveryListener listener = mDiscoveryListeners.get(listenerId);
+ if (listener == null) {
+ Log.w(
+ TAG,
+ "Failed to stop service discovery. Listener ID "
+ + listenerId
+ + ". The listener is null.");
+ return;
+ }
+
+ Log.i(TAG, "Stopping service discovery. Listener: " + listener);
+ mNsdManager.stopServiceDiscovery(listener);
+ }
+
+ @Override
+ public void resolveService(
+ String name, String type, INsdResolveServiceCallback callback, int listenerId) {
+ mHandler.post(() -> resolveServiceInternal(name, type, callback, listenerId));
+ }
+
+ private void resolveServiceInternal(
+ String name, String type, INsdResolveServiceCallback callback, int listenerId) {
+ checkOnHandlerThread();
+
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+ serviceInfo.setServiceName(name);
+ serviceInfo.setServiceType(type);
+ serviceInfo.setNetwork(null);
+ Log.i(
+ TAG,
+ "Resolving service."
+ + " Listener ID: "
+ + listenerId
+ + ", service name: "
+ + name
+ + ", service type: "
+ + type);
+
+ ServiceInfoListener listener = new ServiceInfoListener(serviceInfo, listenerId, callback);
+ mServiceInfoListeners.append(listenerId, listener);
+ mNsdManager.registerServiceInfoCallback(serviceInfo, mExecutor, listener);
+ }
+
+ @Override
+ public void stopServiceResolution(int listenerId) {
+ mHandler.post(() -> stopServiceResolutionInternal(listenerId));
+ }
+
+ private void stopServiceResolutionInternal(int listenerId) {
+ checkOnHandlerThread();
+
+ ServiceInfoListener listener = mServiceInfoListeners.get(listenerId);
+ if (listener == null) {
+ Log.w(
+ TAG,
+ "Failed to stop service resolution. Listener ID: "
+ + listenerId
+ + ". The listener is null.");
+ return;
+ }
+
+ Log.i(TAG, "Stopping service resolution. Listener: " + listener);
+
+ try {
+ mNsdManager.unregisterServiceInfoCallback(listener);
+ } catch (IllegalArgumentException e) {
+ Log.w(
+ TAG,
+ "Failed to stop the service resolution because it's already stopped. Listener: "
+ + listener);
+ }
+ }
+
+ @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(
@@ -178,8 +362,12 @@
}
}
- /** On ot-daemon died, unregister all registrations. */
- public void onOtDaemonDied() {
+ @Override
+ public void reset() {
+ mHandler.post(this::resetInternal);
+ }
+
+ private void resetInternal() {
checkOnHandlerThread();
for (int i = 0; i < mRegistrationListeners.size(); ++i) {
try {
@@ -198,37 +386,9 @@
mRegistrationListeners.clear();
}
- // TODO: b/323300118 - Remove this mechanism when the race condition in NsdManager is fixed.
- /** Fetch the first job from the queue and run it. See the class doc for more details. */
- private void peekAndRun() {
- if (mRegistrationJobs.isEmpty()) {
- return;
- }
- Runnable job = mRegistrationJobs.getFirst();
- job.run();
- }
-
- // TODO: b/323300118 - Remove this mechanism when the race condition in NsdManager is fixed.
- /**
- * Pop the first job from the queue and run the next job. See the class doc for more details.
- */
- private void popAndRunNext() {
- if (mRegistrationJobs.isEmpty()) {
- Log.i(TAG, "No registration jobs when trying to pop and run next.");
- return;
- }
- mRegistrationJobs.removeFirst();
- peekAndRun();
- }
-
- private void postRegistrationJob(Runnable registrationJob) {
- mHandler.post(
- () -> {
- mRegistrationJobs.addLast(registrationJob);
- if (mRegistrationJobs.size() == 1) {
- peekAndRun();
- }
- });
+ /** On ot-daemon died, reset. */
+ public void onOtDaemonDied() {
+ reset();
}
private final class RegistrationListener implements NsdManager.RegistrationListener {
@@ -268,7 +428,6 @@
} catch (RemoteException ignored) {
// do nothing if the client is dead
}
- popAndRunNext();
}
@Override
@@ -290,7 +449,6 @@
// do nothing if the client is dead
}
}
- popAndRunNext();
}
@Override
@@ -308,7 +466,6 @@
} catch (RemoteException ignored) {
// do nothing if the client is dead
}
- popAndRunNext();
}
@Override
@@ -329,7 +486,241 @@
}
}
mRegistrationListeners.remove(mListenerId);
- popAndRunNext();
+ }
+ }
+
+ private final class DiscoveryListener implements NsdManager.DiscoveryListener {
+ private final int mListenerId;
+ private final String mType;
+ private final INsdDiscoverServiceCallback mDiscoverServiceCallback;
+
+ DiscoveryListener(
+ int listenerId,
+ @NonNull String type,
+ @NonNull INsdDiscoverServiceCallback discoverServiceCallback) {
+ mListenerId = listenerId;
+ mType = type;
+ mDiscoverServiceCallback = discoverServiceCallback;
+ }
+
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+ Log.e(
+ TAG,
+ "Failed to start service discovery."
+ + " Error code: "
+ + errorCode
+ + ", listener: "
+ + this);
+ mDiscoveryListeners.remove(mListenerId);
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+ Log.e(
+ TAG,
+ "Failed to stop service discovery."
+ + " Error code: "
+ + errorCode
+ + ", listener: "
+ + this);
+ mDiscoveryListeners.remove(mListenerId);
+ }
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {
+ Log.i(TAG, "Started service discovery. Listener: " + this);
+ }
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {
+ Log.i(TAG, "Stopped service discovery. Listener: " + this);
+ mDiscoveryListeners.remove(mListenerId);
+ }
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ Log.i(TAG, "Found service: " + serviceInfo);
+ try {
+ mDiscoverServiceCallback.onServiceDiscovered(
+ serviceInfo.getServiceName(), mType, true);
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ }
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ Log.i(TAG, "Lost service: " + serviceInfo);
+ try {
+ mDiscoverServiceCallback.onServiceDiscovered(
+ serviceInfo.getServiceName(), mType, false);
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ID: " + mListenerId + ", type: " + mType;
+ }
+ }
+
+ private final class ServiceInfoListener implements NsdManager.ServiceInfoCallback {
+ private final String mName;
+ private final String mType;
+ private final INsdResolveServiceCallback mResolveServiceCallback;
+ private final int mListenerId;
+
+ ServiceInfoListener(
+ @NonNull NsdServiceInfo serviceInfo,
+ int listenerId,
+ @NonNull INsdResolveServiceCallback resolveServiceCallback) {
+ mName = serviceInfo.getServiceName();
+ mType = serviceInfo.getServiceType();
+ mListenerId = listenerId;
+ mResolveServiceCallback = resolveServiceCallback;
+ }
+
+ @Override
+ public void onServiceInfoCallbackRegistrationFailed(int errorCode) {
+ Log.e(
+ TAG,
+ "Failed to register service info callback."
+ + " Listener ID: "
+ + mListenerId
+ + ", error: "
+ + errorCode
+ + ", service name: "
+ + mName
+ + ", service type: "
+ + mType);
+ }
+
+ @Override
+ public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
+ Log.i(
+ TAG,
+ "Service is resolved. "
+ + " Listener ID: "
+ + mListenerId
+ + ", serviceInfo: "
+ + serviceInfo);
+ List<String> addresses = new ArrayList<>();
+ for (InetAddress address : serviceInfo.getHostAddresses()) {
+ if (address instanceof Inet6Address) {
+ addresses.add(address.getHostAddress());
+ }
+ }
+ List<DnsTxtAttribute> txtList = new ArrayList<>();
+ for (Map.Entry<String, byte[]> entry : serviceInfo.getAttributes().entrySet()) {
+ DnsTxtAttribute attribute =
+ new DnsTxtAttribute(entry.getKey(), entry.getValue().clone());
+ txtList.add(attribute);
+ }
+ // TODO: b/329018320 - Use the serviceInfo.getExpirationTime to derive TTL.
+ int ttlSeconds = 10;
+ try {
+ mResolveServiceCallback.onServiceResolved(
+ serviceInfo.getHostname(),
+ serviceInfo.getServiceName(),
+ serviceInfo.getServiceType(),
+ serviceInfo.getPort(),
+ addresses,
+ txtList,
+ ttlSeconds);
+
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ }
+
+ @Override
+ public void onServiceLost() {}
+
+ @Override
+ public void onServiceInfoCallbackUnregistered() {
+ Log.i(TAG, "The service info callback is unregistered. Listener: " + this);
+ mServiceInfoListeners.remove(mListenerId);
+ }
+
+ @Override
+ public String toString() {
+ 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/OperationReceiverWrapper.java b/thread/service/java/com/android/server/thread/OperationReceiverWrapper.java
index a8909bc..bad63f3 100644
--- a/thread/service/java/com/android/server/thread/OperationReceiverWrapper.java
+++ b/thread/service/java/com/android/server/thread/OperationReceiverWrapper.java
@@ -16,9 +16,11 @@
package com.android.server.thread;
+import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
import android.net.thread.IOperationReceiver;
+import android.net.thread.ThreadNetworkException;
import android.os.RemoteException;
import com.android.internal.annotations.GuardedBy;
@@ -29,6 +31,7 @@
/** A {@link IOperationReceiver} wrapper which makes it easier to invoke the callbacks. */
final class OperationReceiverWrapper {
private final IOperationReceiver mReceiver;
+ private final boolean mExpectOtDaemonDied;
private static final Object sPendingReceiversLock = new Object();
@@ -36,7 +39,19 @@
private static final Set<OperationReceiverWrapper> sPendingReceivers = new HashSet<>();
public OperationReceiverWrapper(IOperationReceiver receiver) {
- this.mReceiver = receiver;
+ this(receiver, false /* expectOtDaemonDied */);
+ }
+
+ /**
+ * Creates a new {@link OperationReceiverWrapper}.
+ *
+ * <p>If {@code expectOtDaemonDied} is {@code true}, it's expected that ot-daemon becomes dead
+ * before {@code receiver} is completed with {@code onSuccess} and {@code onError} and {@code
+ * receiver#onSuccess} will be invoked in this case.
+ */
+ public OperationReceiverWrapper(IOperationReceiver receiver, boolean expectOtDaemonDied) {
+ mReceiver = receiver;
+ mExpectOtDaemonDied = expectOtDaemonDied;
synchronized (sPendingReceiversLock) {
sPendingReceivers.add(this);
@@ -47,7 +62,11 @@
synchronized (sPendingReceiversLock) {
for (OperationReceiverWrapper receiver : sPendingReceivers) {
try {
- receiver.mReceiver.onError(ERROR_UNAVAILABLE, "Thread daemon died");
+ if (receiver.mExpectOtDaemonDied) {
+ receiver.mReceiver.onSuccess();
+ } else {
+ receiver.mReceiver.onError(ERROR_UNAVAILABLE, "Thread daemon died");
+ }
} catch (RemoteException e) {
// The client is dead, do nothing
}
@@ -68,6 +87,17 @@
}
}
+ public void onError(Throwable e) {
+ if (e instanceof ThreadNetworkException) {
+ ThreadNetworkException threadException = (ThreadNetworkException) e;
+ onError(threadException.getErrorCode(), threadException.getMessage());
+ } else if (e instanceof RemoteException) {
+ onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ } else {
+ throw new AssertionError(e);
+ }
+ }
+
public void onError(int errorCode, String errorMessage, Object... messageArgs) {
synchronized (sPendingReceiversLock) {
sPendingReceivers.remove(this);
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 0623b87..2f60d9a 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -40,6 +40,7 @@
import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
import static android.net.thread.ThreadNetworkException.ERROR_TIMEOUT;
import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_CHANNEL;
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_OPERATION;
import static android.net.thread.ThreadNetworkManager.DISALLOW_THREAD_NETWORK;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
@@ -47,6 +48,7 @@
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_BUSY;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_FAILED_PRECONDITION;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
+import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_NOT_IMPLEMENTED;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_NO_BUFS;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_PARSE;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_REASSEMBLY_TIMEOUT;
@@ -59,6 +61,8 @@
import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -68,9 +72,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+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;
@@ -85,15 +89,19 @@
import android.net.TestNetworkSpecifier;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
+import android.net.thread.ChannelMaxPower;
import android.net.thread.IActiveOperationalDatasetReceiver;
+import android.net.thread.IConfigurationReceiver;
import android.net.thread.IOperationReceiver;
import android.net.thread.IOperationalDatasetCallback;
import android.net.thread.IStateCallback;
import android.net.thread.IThreadNetworkController;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkController.DeviceRole;
+import android.net.thread.ThreadNetworkException;
import android.net.thread.ThreadNetworkException.ErrorCode;
import android.os.Build;
import android.os.Handler;
@@ -103,25 +111,33 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
+import com.android.connectivity.resources.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceManagerWrapper;
+import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.BackboneRouterState;
import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
+import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.IChannelMasksReceiver;
import com.android.server.thread.openthread.IOtDaemon;
import com.android.server.thread.openthread.IOtDaemonCallback;
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.security.SecureRandom;
+import java.time.Clock;
+import java.time.DateTimeException;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
@@ -129,6 +145,7 @@
import java.util.Objects;
import java.util.Random;
import java.util.function.Supplier;
+import java.util.regex.Pattern;
/**
* Implementation of the {@link ThreadNetworkController} API.
@@ -143,6 +160,19 @@
final class ThreadNetworkControllerService extends IThreadNetworkController.Stub {
private static final String TAG = "ThreadNetworkService";
+ // The model name length in utf-8 bytes
+ private static final int MAX_MODEL_NAME_UTF8_BYTES = 24;
+
+ // The max vendor name length in utf-8 bytes
+ private static final int MAX_VENDOR_NAME_UTF8_BYTES = 24;
+
+ // This regex pattern allows "XXXXXX", "XX:XX:XX" and "XX-XX-XX" OUI formats.
+ // 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;
@@ -159,7 +189,13 @@
private final InfraInterfaceController mInfraIfController;
private final NsdPublisher mNsdPublisher;
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
+ private final ConnectivityResources mResources;
+ private final Supplier<String> mCountryCodeSupplier;
+ private final Map<IConfigurationReceiver, IBinder.DeathRecipient> mConfigurationReceivers =
+ new HashMap<>();
+ // This should not be directly used for calling IOtDaemon APIs because ot-daemon may die and
+ // {@code mOtDaemon} will be set to {@code null}. Instead, use {@code getOtDaemon()}
@Nullable private IOtDaemon mOtDaemon;
@Nullable private NetworkAgent mNetworkAgent;
@Nullable private NetworkAgent mTestNetworkAgent;
@@ -174,6 +210,8 @@
private final ThreadPersistentSettings mPersistentSettings;
private final UserManager mUserManager;
private boolean mUserRestricted;
+ private boolean mAirplaneModeOn;
+ private boolean mForceStopOtDaemonEnabled;
private BorderRouterConfigurationParcel mBorderRouterConfig;
@@ -188,7 +226,9 @@
InfraInterfaceController infraIfController,
ThreadPersistentSettings persistentSettings,
NsdPublisher nsdPublisher,
- UserManager userManager) {
+ UserManager userManager,
+ ConnectivityResources resources,
+ Supplier<String> countryCodeSupplier) {
mContext = context;
mHandler = handler;
mNetworkProvider = networkProvider;
@@ -202,10 +242,14 @@
mPersistentSettings = persistentSettings;
mNsdPublisher = nsdPublisher;
mUserManager = userManager;
+ mResources = resources;
+ mCountryCodeSupplier = countryCodeSupplier;
}
public static ThreadNetworkControllerService newInstance(
- Context context, ThreadPersistentSettings persistentSettings) {
+ Context context,
+ ThreadPersistentSettings persistentSettings,
+ Supplier<String> countryCodeSupplier) {
HandlerThread handlerThread = new HandlerThread("ThreadHandlerThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
@@ -222,39 +266,9 @@
new InfraInterfaceController(),
persistentSettings,
NsdPublisher.newInstance(context, handler),
- context.getSystemService(UserManager.class));
- }
-
- 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 */);
+ context.getSystemService(UserManager.class),
+ new ConnectivityResources(context),
+ countryCodeSupplier);
}
private NetworkRequest newUpstreamNetworkRequest() {
@@ -279,17 +293,31 @@
.build();
}
- private void initializeOtDaemon() {
+ private void maybeInitializeOtDaemon() {
+ if (!shouldEnableThread()) {
+ return;
+ }
+
+ Log.i(TAG, "Starting OT daemon...");
+
try {
getOtDaemon();
} catch (RemoteException e) {
- Log.e(TAG, "Failed to initialize ot-daemon");
+ Log.e(TAG, "Failed to initialize ot-daemon", e);
+ } catch (ThreadNetworkException e) {
+ // no ThreadNetworkException.ERROR_THREAD_DISABLED error should be thrown
+ throw new AssertionError(e);
}
}
- private IOtDaemon getOtDaemon() throws RemoteException {
+ private IOtDaemon getOtDaemon() throws RemoteException, ThreadNetworkException {
checkOnHandlerThread();
+ if (mForceStopOtDaemonEnabled) {
+ throw new ThreadNetworkException(
+ ERROR_THREAD_DISABLED, "ot-daemon is forcibly stopped");
+ }
+
if (mOtDaemon != null) {
return mOtDaemon;
}
@@ -298,29 +326,89 @@
if (otDaemon == null) {
throw new RemoteException("Internal error: failed to start OT daemon");
}
- otDaemon.initialize(mTunIfController.getTunFd(), isEnabled(), mNsdPublisher);
- otDaemon.registerStateCallback(mOtDaemonCallbackProxy, -1);
+
+ otDaemon.initialize(
+ mTunIfController.getTunFd(),
+ shouldEnableThread(),
+ mNsdPublisher,
+ getMeshcopTxtAttributes(mResources.get()),
+ mOtDaemonCallbackProxy,
+ mCountryCodeSupplier.get());
otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
mOtDaemon = otDaemon;
return mOtDaemon;
}
+ @VisibleForTesting
+ static MeshcopTxtAttributes getMeshcopTxtAttributes(Resources resources) {
+ final String modelName = resources.getString(R.string.config_thread_model_name);
+ final String vendorName = resources.getString(R.string.config_thread_vendor_name);
+ final String vendorOui = resources.getString(R.string.config_thread_vendor_oui);
+ final boolean managedByGoogle =
+ resources.getBoolean(R.bool.config_thread_managed_by_google_home);
+
+ if (!modelName.isEmpty()) {
+ if (modelName.getBytes(UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
+ throw new IllegalStateException(
+ "Model name is longer than "
+ + MAX_MODEL_NAME_UTF8_BYTES
+ + "utf-8 bytes: "
+ + modelName);
+ }
+ }
+
+ if (!vendorName.isEmpty()) {
+ if (vendorName.getBytes(UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
+ throw new IllegalStateException(
+ "Vendor name is longer than "
+ + MAX_VENDOR_NAME_UTF8_BYTES
+ + " utf-8 bytes: "
+ + vendorName);
+ }
+ }
+
+ if (!vendorOui.isEmpty() && !Pattern.compile(OUI_REGEX).matcher(vendorOui).matches()) {
+ throw new IllegalStateException("Vendor OUI is invalid: " + vendorOui);
+ }
+
+ MeshcopTxtAttributes meshcopTxts = new MeshcopTxtAttributes();
+ meshcopTxts.modelName = modelName;
+ meshcopTxts.vendorName = vendorName;
+ meshcopTxts.vendorOui = HexEncoding.decode(vendorOui.replace("-", "").replace(":", ""));
+ meshcopTxts.nonStandardTxtEntries = List.of(makeManagedByGoogleTxtAttr(managedByGoogle));
+
+ return meshcopTxts;
+ }
+
+ /**
+ * Creates a DNS-SD TXT entry for indicating whether Thread on this device is managed by Google.
+ *
+ * @return TXT entry "vgh=1" if {@code managedByGoogle} is {@code true}; otherwise, "vgh=0"
+ */
+ private static DnsTxtAttribute makeManagedByGoogleTxtAttr(boolean managedByGoogle) {
+ final byte[] value = (managedByGoogle ? "1" : "0").getBytes(UTF_8);
+ return new DnsTxtAttribute("vgh", value);
+ }
+
private void onOtDaemonDied() {
checkOnHandlerThread();
- Log.w(TAG, "OT daemon is dead, clean up and restart it...");
+ Log.w(TAG, "OT daemon is dead, clean up...");
OperationReceiverWrapper.onOtDaemonDied();
mOtDaemonCallbackProxy.onOtDaemonDied();
mTunIfController.onOtDaemonDied();
mNsdPublisher.onOtDaemonDied();
mOtDaemon = null;
- initializeOtDaemon();
+ maybeInitializeOtDaemon();
}
public void initialize() {
mHandler.post(
() -> {
- Log.d(TAG, "Initializing Thread system service...");
+ Log.d(
+ TAG,
+ "Initializing Thread system service: Thread is "
+ + (shouldEnableThread() ? "enabled" : "disabled"));
try {
mTunIfController.createTunInterface();
} catch (IOException e) {
@@ -332,10 +420,61 @@
requestThreadNetwork();
mUserRestricted = isThreadUserRestricted();
registerUserRestrictionsReceiver();
- initializeOtDaemon();
+ mAirplaneModeOn = isAirplaneModeOn();
+ registerAirplaneModeReceiver();
+ maybeInitializeOtDaemon();
});
}
+ /**
+ * Force stops ot-daemon immediately and prevents ot-daemon from being restarted by
+ * system_server again.
+ *
+ * <p>This is for VTS testing only.
+ */
+ @RequiresPermission(PERMISSION_THREAD_NETWORK_PRIVILEGED)
+ void forceStopOtDaemonForTest(boolean enabled, @NonNull IOperationReceiver receiver) {
+ enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+
+ mHandler.post(
+ () ->
+ forceStopOtDaemonForTestInternal(
+ enabled,
+ new OperationReceiverWrapper(
+ receiver, true /* expectOtDaemonDied */)));
+ }
+
+ private void forceStopOtDaemonForTestInternal(
+ boolean enabled, @NonNull OperationReceiverWrapper receiver) {
+ checkOnHandlerThread();
+ if (enabled == mForceStopOtDaemonEnabled) {
+ receiver.onSuccess();
+ return;
+ }
+
+ if (!enabled) {
+ mForceStopOtDaemonEnabled = false;
+ maybeInitializeOtDaemon();
+ receiver.onSuccess();
+ return;
+ }
+
+ try {
+ getOtDaemon().terminate();
+ // Do not invoke the {@code receiver} callback here but wait for ot-daemon to
+ // become dead, so that it's guaranteed that ot-daemon is stopped when {@code
+ // receiver} is completed
+ } catch (RemoteException e) {
+ Log.e(TAG, "otDaemon.terminate failed", e);
+ receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ } catch (ThreadNetworkException e) {
+ // No ThreadNetworkException.ERROR_THREAD_DISABLED error will be thrown
+ throw new AssertionError(e);
+ } finally {
+ mForceStopOtDaemonEnabled = true;
+ }
+ }
+
public void setEnabled(boolean isEnabled, @NonNull IOperationReceiver receiver) {
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
@@ -349,6 +488,7 @@
private void setEnabledInternal(
boolean isEnabled, boolean persist, @NonNull OperationReceiverWrapper receiver) {
+ checkOnHandlerThread();
if (isEnabled && isThreadUserRestricted()) {
receiver.onError(
ERROR_FAILED_PRECONDITION,
@@ -356,32 +496,112 @@
return;
}
+ Log.i(TAG, "Set Thread enabled: " + isEnabled + ", persist: " + persist);
+
if (persist) {
// The persistent setting keeps the desired enabled state, thus it's set regardless
// 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 {
getOtDaemon().setThreadEnabled(isEnabled, newOtStatusReceiver(receiver));
- } catch (RemoteException e) {
+ } catch (RemoteException | ThreadNetworkException e) {
Log.e(TAG, "otDaemon.setThreadEnabled failed", e);
- receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ receiver.onError(e);
}
}
+ @Override
+ public void setConfiguration(
+ @NonNull ThreadConfiguration configuration, @NonNull IOperationReceiver receiver) {
+ enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+ mHandler.post(() -> setConfigurationInternal(configuration, receiver));
+ }
+
+ private void setConfigurationInternal(
+ @NonNull ThreadConfiguration configuration,
+ @NonNull IOperationReceiver operationReceiver) {
+ checkOnHandlerThread();
+
+ Log.i(TAG, "Set Thread configuration: " + configuration);
+
+ final boolean changed = mPersistentSettings.putConfiguration(configuration);
+ try {
+ operationReceiver.onSuccess();
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ if (changed) {
+ for (IConfigurationReceiver configReceiver : mConfigurationReceivers.keySet()) {
+ try {
+ configReceiver.onConfigurationChanged(configuration);
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ }
+ }
+ }
+
+ @Override
+ public void registerConfigurationCallback(@NonNull IConfigurationReceiver callback) {
+ enforceAllPermissionsGranted(permission.THREAD_NETWORK_PRIVILEGED);
+ mHandler.post(() -> registerConfigurationCallbackInternal(callback));
+ }
+
+ private void registerConfigurationCallbackInternal(@NonNull IConfigurationReceiver callback) {
+ checkOnHandlerThread();
+ if (mConfigurationReceivers.containsKey(callback)) {
+ throw new IllegalStateException("Registering the same IConfigurationReceiver twice");
+ }
+ IBinder.DeathRecipient deathRecipient =
+ () -> mHandler.post(() -> unregisterConfigurationCallbackInternal(callback));
+ try {
+ callback.asBinder().linkToDeath(deathRecipient, 0);
+ } catch (RemoteException e) {
+ return;
+ }
+ mConfigurationReceivers.put(callback, deathRecipient);
+ try {
+ callback.onConfigurationChanged(mPersistentSettings.getConfiguration());
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ }
+
+ @Override
+ public void unregisterConfigurationCallback(@NonNull IConfigurationReceiver callback) {
+ enforceAllPermissionsGranted(permission.THREAD_NETWORK_PRIVILEGED);
+ mHandler.post(() -> unregisterConfigurationCallbackInternal(callback));
+ }
+
+ private void unregisterConfigurationCallbackInternal(@NonNull IConfigurationReceiver callback) {
+ checkOnHandlerThread();
+ if (!mConfigurationReceivers.containsKey(callback)) {
+ return;
+ }
+ callback.asBinder().unlinkToDeath(mConfigurationReceivers.remove(callback), 0);
+ }
+
private void registerUserRestrictionsReceiver() {
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- onUserRestrictionsChanged(isThreadUserRestricted());
+ mHandler.post(() -> onUserRestrictionsChanged(isThreadUserRestricted()));
}
},
- new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED),
- null /* broadcastPermission */,
- mHandler);
+ new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
}
private void onUserRestrictionsChanged(boolean newUserRestrictedState) {
@@ -397,34 +617,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 !mUserRestricted && mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
+ setEnabledInternal(
+ shouldEnableThread, false /* persist */, new OperationReceiverWrapper(receiver));
}
/** Returns {@code true} if Thread has been restricted for the user. */
@@ -432,6 +648,75 @@
return mUserManager.hasUserRestriction(DISALLOW_THREAD_NETWORK);
}
+ private void registerAirplaneModeReceiver() {
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mHandler.post(() -> onAirplaneModeChanged(isAirplaneModeOn()));
+ }
+ },
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+ }
+
+ 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.");
@@ -489,7 +774,14 @@
@Override
public void onAvailable(@NonNull Network network) {
checkOnHandlerThread();
- Log.i(TAG, "Thread network available: " + network);
+ Log.i(TAG, "Thread network is available: " + network);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ checkOnHandlerThread();
+ Log.i(TAG, "Thread network is lost: " + network);
+ disableBorderRouting();
}
@Override
@@ -504,7 +796,7 @@
+ localNetworkInfo
+ "}");
if (localNetworkInfo.getUpstreamNetwork() == null) {
- mUpstreamNetwork = null;
+ disableBorderRouting();
return;
}
if (!localNetworkInfo.getUpstreamNetwork().equals(mUpstreamNetwork)) {
@@ -512,6 +804,7 @@
if (mNetworkToInterface.containsKey(mUpstreamNetwork)) {
enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
}
+ mNsdPublisher.setNetworkForHostResolution(mUpstreamNetwork);
}
}
}
@@ -523,6 +816,7 @@
// requirement.
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
.build(),
new ThreadNetworkCallback(),
mHandler);
@@ -543,6 +837,7 @@
new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
.build();
final NetworkScore score =
@@ -604,9 +899,9 @@
try {
getOtDaemon().getChannelMasks(newChannelMasksReceiver(networkName, receiver));
- } catch (RemoteException e) {
+ } catch (RemoteException | ThreadNetworkException e) {
Log.e(TAG, "otDaemon.getChannelMasks failed", e);
- receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ receiver.onError(e);
}
}
@@ -620,7 +915,6 @@
networkName,
supportedChannelMask,
preferredChannelMask,
- Instant.now(),
new Random(),
new SecureRandom());
@@ -638,9 +932,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;
@@ -652,9 +955,7 @@
final byte[] securityFlags = new byte[] {(byte) 0xff, (byte) 0xf8};
return new ActiveOperationalDataset.Builder()
- .setActiveTimestamp(
- new OperationalDatasetTimestamp(
- now.getEpochSecond() & 0xffffffffffffL, 0, false))
+ .setActiveTimestamp(OperationalDatasetTimestamp.fromInstant(now, authoritative))
.setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
.setPanId(panId)
.setNetworkName(networkName)
@@ -669,6 +970,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;
@@ -752,7 +1061,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());
}
}
@@ -778,6 +1091,8 @@
return ERROR_ABORTED;
case OT_ERROR_BUSY:
return ERROR_BUSY;
+ case OT_ERROR_NOT_IMPLEMENTED:
+ return ERROR_UNSUPPORTED_OPERATION;
case OT_ERROR_NO_BUFS:
return ERROR_RESOURCE_EXHAUSTED;
case OT_ERROR_PARSE:
@@ -816,9 +1131,9 @@
try {
// The otDaemon.join() will leave first if this device is currently attached
getOtDaemon().join(activeDataset.toThreadTlvs(), newOtStatusReceiver(receiver));
- } catch (RemoteException e) {
+ } catch (RemoteException | ThreadNetworkException e) {
Log.e(TAG, "otDaemon.join failed", e);
- receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ receiver.onError(e);
}
}
@@ -841,14 +1156,14 @@
getOtDaemon()
.scheduleMigration(
pendingDataset.toThreadTlvs(), newOtStatusReceiver(receiver));
- } catch (RemoteException e) {
+ } catch (RemoteException | ThreadNetworkException e) {
Log.e(TAG, "otDaemon.scheduleMigration failed", e);
- receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ receiver.onError(e);
}
}
@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)));
@@ -859,9 +1174,9 @@
try {
getOtDaemon().leave(newOtStatusReceiver(receiver));
- } catch (RemoteException e) {
- // Oneway AIDL API should never throw?
- receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ } catch (RemoteException | ThreadNetworkException e) {
+ Log.e(TAG, "otDaemon.leave failed", e);
+ receiver.onError(e);
}
}
@@ -883,11 +1198,18 @@
String countryCode, @NonNull OperationReceiverWrapper receiver) {
checkOnHandlerThread();
+ // Fails early to avoid waking up ot-daemon by the ThreadNetworkCountryCode class
+ if (!shouldEnableThread()) {
+ receiver.onError(
+ ERROR_THREAD_DISABLED, "Can't set country code when Thread is disabled");
+ return;
+ }
+
try {
getOtDaemon().setCountryCode(countryCode, newOtStatusReceiver(receiver));
- } catch (RemoteException e) {
+ } catch (RemoteException | ThreadNetworkException e) {
Log.e(TAG, "otDaemon.setCountryCode failed", e);
- receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ receiver.onError(e);
}
}
@@ -923,6 +1245,30 @@
}
}
+ @RequiresPermission(PERMISSION_THREAD_NETWORK_PRIVILEGED)
+ public void setChannelMaxPowers(
+ @NonNull ChannelMaxPower[] channelMaxPowers, @NonNull IOperationReceiver receiver) {
+ enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+
+ mHandler.post(
+ () ->
+ setChannelMaxPowersInternal(
+ channelMaxPowers, new OperationReceiverWrapper(receiver)));
+ }
+
+ private void setChannelMaxPowersInternal(
+ @NonNull ChannelMaxPower[] channelMaxPowers,
+ @NonNull OperationReceiverWrapper receiver) {
+ checkOnHandlerThread();
+
+ try {
+ getOtDaemon().setChannelMaxPowers(channelMaxPowers, newOtStatusReceiver(receiver));
+ } catch (RemoteException | ThreadNetworkException e) {
+ Log.e(TAG, "otDaemon.setChannelMaxPowers failed", e);
+ receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ }
+ }
+
private void enableBorderRouting(String infraIfName) {
if (mBorderRouterConfig.isBorderRoutingEnabled
&& infraIfName.equals(mBorderRouterConfig.infraInterfaceName)) {
@@ -935,24 +1281,25 @@
mInfraIfController.createIcmp6Socket(infraIfName);
mBorderRouterConfig.isBorderRoutingEnabled = true;
- mOtDaemon.configureBorderRouter(
- mBorderRouterConfig,
- new IOtStatusReceiver.Stub() {
- @Override
- public void onSuccess() {
- Log.i(TAG, "configure border router successfully");
- }
+ getOtDaemon()
+ .configureBorderRouter(
+ mBorderRouterConfig, new ConfigureBorderRouterStatusReceiver());
+ } catch (RemoteException | IOException | ThreadNetworkException e) {
+ Log.w(TAG, "Failed to enable border routing", e);
+ }
+ }
- @Override
- public void onError(int i, String s) {
- Log.w(
- TAG,
- String.format(
- "failed to configure border router: %d %s", i, s));
- }
- });
- } catch (Exception e) {
- Log.w(TAG, "enableBorderRouting failed: " + e);
+ private void disableBorderRouting() {
+ mUpstreamNetwork = null;
+ mBorderRouterConfig.infraInterfaceName = null;
+ mBorderRouterConfig.infraInterfaceIcmp6Socket = null;
+ mBorderRouterConfig.isBorderRoutingEnabled = false;
+ try {
+ getOtDaemon()
+ .configureBorderRouter(
+ mBorderRouterConfig, new ConfigureBorderRouterStatusReceiver());
+ } catch (RemoteException | ThreadNetworkException e) {
+ Log.w(TAG, "Failed to disable border routing", e);
}
}
@@ -981,20 +1328,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
@@ -1073,6 +1422,20 @@
}
}
+ private static final class ConfigureBorderRouterStatusReceiver extends IOtStatusReceiver.Stub {
+ public ConfigureBorderRouterStatusReceiver() {}
+
+ @Override
+ public void onSuccess() {
+ Log.i(TAG, "Configured border router successfully");
+ }
+
+ @Override
+ public void onError(int i, String s) {
+ Log.w(TAG, String.format("Failed to configure border router: %d %s", i, s));
+ }
+ }
+
/**
* Handles and forwards Thread daemon callbacks. This class must be accessed from the thread of
* {@code mHandler}.
@@ -1105,17 +1468,8 @@
try {
getOtDaemon().registerStateCallback(this, callbackMetadata.id);
- } catch (RemoteException e) {
- // oneway operation should never fail
- }
- }
-
- 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
+ } catch (RemoteException | ThreadNetworkException e) {
+ Log.e(TAG, "otDaemon.registerStateCallback failed", e);
}
}
@@ -1146,8 +1500,8 @@
try {
getOtDaemon().registerStateCallback(this, callbackMetadata.id);
- } catch (RemoteException e) {
- // oneway operation should never fail
+ } catch (RemoteException | ThreadNetworkException e) {
+ Log.e(TAG, "otDaemon.registerStateCallback failed", e);
}
}
@@ -1166,8 +1520,11 @@
return;
}
+ final int deviceRole = mState.deviceRole;
+ mState = null;
+
// If this device is already STOPPED or DETACHED, do nothing
- if (!ThreadNetworkController.isAttached(mState.deviceRole)) {
+ if (!ThreadNetworkController.isAttached(deviceRole)) {
return;
}
@@ -1182,15 +1539,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
+ }
}
}
@@ -1217,6 +1578,7 @@
onInterfaceStateChanged(newState.isInterfaceUp);
onDeviceRoleChanged(newState.deviceRole, listenerId);
onPartitionIdChanged(newState.partitionId, listenerId);
+ onThreadEnabledChanged(newState.threadEnabled, listenerId);
mState = newState;
ActiveOperationalDataset newActiveDataset;
@@ -1326,13 +1688,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/ThreadNetworkCountryCode.java b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
index ffa7b44..a194114 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
@@ -16,6 +16,8 @@
package com.android.server.thread;
+import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE;
+
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.TargetApi;
@@ -83,6 +85,7 @@
COUNTRY_CODE_SOURCE_TELEPHONY,
COUNTRY_CODE_SOURCE_TELEPHONY_LAST,
COUNTRY_CODE_SOURCE_WIFI,
+ COUNTRY_CODE_SOURCE_SETTINGS,
})
private @interface CountryCodeSource {}
@@ -93,6 +96,7 @@
private static final String COUNTRY_CODE_SOURCE_TELEPHONY = "Telephony";
private static final String COUNTRY_CODE_SOURCE_TELEPHONY_LAST = "TelephonyLast";
private static final String COUNTRY_CODE_SOURCE_WIFI = "Wifi";
+ private static final String COUNTRY_CODE_SOURCE_SETTINGS = "Settings";
private static final CountryCodeInfo DEFAULT_COUNTRY_CODE_INFO =
new CountryCodeInfo(DEFAULT_COUNTRY_CODE, COUNTRY_CODE_SOURCE_DEFAULT);
@@ -107,6 +111,7 @@
private final SubscriptionManager mSubscriptionManager;
private final Map<Integer, TelephonyCountryCodeSlotInfo> mTelephonyCountryCodeSlotInfoMap =
new ArrayMap();
+ private final ThreadPersistentSettings mPersistentSettings;
@Nullable private CountryCodeInfo mCurrentCountryCodeInfo;
@Nullable private CountryCodeInfo mLocationCountryCodeInfo;
@@ -215,7 +220,8 @@
Context context,
TelephonyManager telephonyManager,
SubscriptionManager subscriptionManager,
- @Nullable String oemCountryCode) {
+ @Nullable String oemCountryCode,
+ ThreadPersistentSettings persistentSettings) {
mLocationManager = locationManager;
mThreadNetworkControllerService = threadNetworkControllerService;
mGeocoder = geocoder;
@@ -224,14 +230,19 @@
mContext = context;
mTelephonyManager = telephonyManager;
mSubscriptionManager = subscriptionManager;
+ mPersistentSettings = persistentSettings;
if (oemCountryCode != null) {
mOemCountryCodeInfo = new CountryCodeInfo(oemCountryCode, COUNTRY_CODE_SOURCE_OEM);
}
+
+ mCurrentCountryCodeInfo = pickCountryCode();
}
public static ThreadNetworkCountryCode newInstance(
- Context context, ThreadNetworkControllerService controllerService) {
+ Context context,
+ ThreadNetworkControllerService controllerService,
+ ThreadPersistentSettings persistentSettings) {
return new ThreadNetworkCountryCode(
context.getSystemService(LocationManager.class),
controllerService,
@@ -241,7 +252,8 @@
context,
context.getSystemService(TelephonyManager.class),
context.getSystemService(SubscriptionManager.class),
- ThreadNetworkProperties.country_code().orElse(null));
+ ThreadNetworkProperties.country_code().orElse(null),
+ persistentSettings);
}
/** Sets up this country code module to listen to location country code changes. */
@@ -485,6 +497,11 @@
return mLocationCountryCodeInfo;
}
+ String settingsCountryCode = mPersistentSettings.get(THREAD_COUNTRY_CODE);
+ if (settingsCountryCode != null) {
+ return new CountryCodeInfo(settingsCountryCode, COUNTRY_CODE_SOURCE_SETTINGS);
+ }
+
if (mOemCountryCodeInfo != null) {
return mOemCountryCodeInfo;
}
@@ -498,6 +515,8 @@
public void onSuccess() {
synchronized ("ThreadNetworkCountryCode.this") {
mCurrentCountryCodeInfo = countryCodeInfo;
+ mPersistentSettings.put(
+ THREAD_COUNTRY_CODE.key, countryCodeInfo.getCountryCode());
}
}
@@ -536,10 +555,9 @@
newOperationReceiver(countryCodeInfo));
}
- /** Returns the current country code or {@code null} if no country code is set. */
- @Nullable
+ /** Returns the current country code. */
public synchronized String getCountryCode() {
- return (mCurrentCountryCodeInfo != null) ? mCurrentCountryCodeInfo.getCountryCode() : null;
+ return mCurrentCountryCodeInfo.getCountryCode();
}
/**
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
index 5664922..4c22278 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
@@ -18,6 +18,8 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -58,15 +60,22 @@
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mPersistentSettings.initialize();
mControllerService =
- ThreadNetworkControllerService.newInstance(mContext, mPersistentSettings);
+ ThreadNetworkControllerService.newInstance(
+ mContext, mPersistentSettings, () -> mCountryCode.getCountryCode());
+ mCountryCode =
+ ThreadNetworkCountryCode.newInstance(
+ mContext, mControllerService, mPersistentSettings);
mControllerService.initialize();
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
// Country code initialization is delayed to the BOOT_COMPLETED phase because it will
// call into Wi-Fi and Telephony service whose country code module is ready after
// PHASE_ACTIVITY_MANAGER_READY and PHASE_THIRD_PARTY_APPS_CAN_START
- mCountryCode = ThreadNetworkCountryCode.newInstance(mContext, mControllerService);
mCountryCode.initialize();
- mShellCommand = new ThreadNetworkShellCommand(mCountryCode);
+ mShellCommand =
+ 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 c17c5a7..54155ee 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
@@ -17,36 +17,57 @@
package com.android.server.thread;
import android.annotation.Nullable;
-import android.os.Binder;
-import android.os.Process;
+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.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.util.List;
+import java.time.Duration;
+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 {
- private static final String TAG = "ThreadNetworkShellCommand";
+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");
+ private final Context mContext;
+ private final ThreadNetworkControllerService mControllerService;
+ private final ThreadNetworkCountryCode mCountryCode;
- @Nullable private final ThreadNetworkCountryCode mCountryCode;
@Nullable private PrintWriter mOutputWriter;
@Nullable private PrintWriter mErrorWriter;
- ThreadNetworkShellCommand(@Nullable ThreadNetworkCountryCode countryCode) {
+ public ThreadNetworkShellCommand(
+ Context context,
+ ThreadNetworkControllerService controllerService,
+ ThreadNetworkCountryCode countryCode) {
+ mContext = context;
+ mControllerService = controllerService;
mCountryCode = countryCode;
}
@@ -65,77 +86,205 @@
}
@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;
-
- if (mCountryCode == null) {
- perr.println("Thread country code operations are not supported");
- return -1;
- }
-
- 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":
- if (mCountryCode == null) {
- perr.println("Thread country code operations are not supported");
- return -1;
- }
-
- 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, 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 {
+ enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+ } catch (IllegalArgumentException e) {
+ errorWriter.println("Invalid argument: " + e.getMessage());
+ return -1;
+ }
+
+ CompletableFuture<Void> forceStopFuture = new CompletableFuture<>();
+ mControllerService.forceStopOtDaemonForTest(enabled, newOperationReceiver(forceStopFuture));
+ 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
+ public void onSuccess() {
+ future.complete(null);
+ }
+
+ @Override
+ public void onError(int errorCode, String errorMessage) {
+ future.completeExceptionally(new ThreadNetworkException(errorCode, errorMessage));
+ }
+ };
+ }
+
+ /**
+ * Waits for the future to complete within given timeout.
+ *
+ * <p>Returns 0 if {@code future} completed successfully, or -1 if {@code future} failed to
+ * complete. When failed, error messages are printed to {@code errorWriter}.
+ */
+ private int waitForFuture(
+ CompletableFuture<Void> future, Duration timeout, PrintWriter errorWriter) {
+ try {
+ future.get(timeout.toSeconds(), TimeUnit.SECONDS);
+ return 0;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ errorWriter.println("Failed: " + e.getMessage());
+ } catch (ExecutionException e) {
+ errorWriter.println("Failed: " + e.getCause().getMessage());
+ } catch (TimeoutException e) {
+ errorWriter.println("Failed: command timeout for " + timeout);
+ }
+
+ return -1;
+ }
+
private static boolean argTrueOrFalse(String arg, String trueString, String falseString) {
if (trueString.equals(arg)) {
return true;
@@ -157,27 +306,4 @@
String nextArg = getNextArgRequired();
return argTrueOrFalse(nextArg, trueString, falseString);
}
-
- private void onHelpNonPrivileged(PrintWriter pw) {
- 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");
- }
-
- @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 aba4193..747cc96 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -18,9 +18,11 @@
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ApexEnvironment;
import android.content.Context;
+import android.net.thread.ThreadConfiguration;
import android.os.PersistableBundle;
import android.util.AtomicFile;
import android.util.Log;
@@ -46,10 +48,13 @@
*/
public class ThreadPersistentSettings {
private static final String TAG = "ThreadPersistentSettings";
+
/** File name used for storing settings. */
private static final String FILE_NAME = "ThreadPersistentSettings.xml";
+
/** Current config store data version. This will be incremented for any additions. */
private static final int CURRENT_SETTINGS_STORE_DATA_VERSION = 1;
+
/**
* Stores the version of the data. This can be used to handle migration of data if some
* non-backward compatible change introduced.
@@ -58,7 +63,28 @@
/******** Thread persistent setting keys ***************/
/** 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);
+ 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);
+
+ /** Stores the Thread NAT64 feature toggle state, true for enabled and false for disabled. */
+ private static final Key<Boolean> CONFIG_NAT64_ENABLED =
+ new Key<>("config_nat64_enabled", false);
+
+ /**
+ * Stores the Thread DHCPv6-PD feature toggle state, true for enabled and false for disabled.
+ */
+ private static final Key<Boolean> CONFIG_DHCP6_PD_ENABLED =
+ new Key<>("config_dhcp6_pd_enabled", false);
/******** Thread persistent setting keys ***************/
@@ -120,7 +146,9 @@
private <T> T getObject(String key, T defaultValue) {
Object value;
synchronized (mLock) {
- if (defaultValue instanceof Boolean) {
+ if (defaultValue == null) {
+ value = mSettings.getString(key, null);
+ } else if (defaultValue instanceof Boolean) {
value = mSettings.getBoolean(key, (Boolean) defaultValue);
} else if (defaultValue instanceof Integer) {
value = mSettings.getInt(key, (Integer) defaultValue);
@@ -159,6 +187,30 @@
}
/**
+ * Store a {@link ThreadConfiguration} to the persistent settings.
+ *
+ * @param configuration {@link ThreadConfiguration} to be stored.
+ * @return {@code true} if the configuration was changed, {@code false} otherwise.
+ */
+ public boolean putConfiguration(@NonNull ThreadConfiguration configuration) {
+ if (getConfiguration().equals(configuration)) {
+ return false;
+ }
+ putObject(CONFIG_NAT64_ENABLED.key, configuration.isNat64Enabled());
+ putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcp6PdEnabled());
+ writeToStoreFile();
+ return true;
+ }
+
+ /** Retrieve the {@link ThreadConfiguration} from the persistent settings. */
+ public ThreadConfiguration getConfiguration() {
+ return new ThreadConfiguration.Builder()
+ .setNat64Enabled(get(CONFIG_NAT64_ENABLED))
+ .setDhcp6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED))
+ .build();
+ }
+
+ /**
* Base class to store string key and its default value.
*
* @param <T> Type of the value.
@@ -210,7 +262,7 @@
mSettings.putAll(bundleRead);
}
} catch (FileNotFoundException e) {
- Log.e(TAG, "No store file to read", e);
+ Log.w(TAG, "No store file to read", e);
} catch (IOException e) {
Log.e(TAG, "Read from store file failed", e);
}
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/service/jni/com_android_server_thread_InfraInterfaceController.cpp b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
index 5d24eab..1f260f2 100644
--- a/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
+++ b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
@@ -42,15 +42,8 @@
namespace android {
static jint
-com_android_server_thread_InfraInterfaceController_createIcmp6Socket(JNIEnv *env, jobject clazz,
- jstring interfaceName) {
- ScopedUtfChars ifName(env, interfaceName);
-
- struct icmp6_filter filter;
- constexpr int kEnable = 1;
- constexpr int kIpv6ChecksumOffset = 2;
- constexpr int kHopLimit = 255;
-
+com_android_server_thread_InfraInterfaceController_createFilteredIcmp6Socket(JNIEnv *env,
+ jobject clazz) {
// Initializes the ICMPv6 socket.
int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (sock == -1) {
@@ -59,6 +52,7 @@
return -1;
}
+ struct icmp6_filter filter;
// Only accept Router Advertisements, Router Solicitations and Neighbor
// Advertisements.
ICMP6_FILTER_SETBLOCKALL(&filter);
@@ -73,53 +67,6 @@
return -1;
}
- // We want a source address and interface index.
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &kEnable, sizeof(kEnable)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVPKTINFO (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &kIpv6ChecksumOffset,
- sizeof(kIpv6ChecksumOffset)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_CHECKSUM (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- // We need to be able to reject RAs arriving from off-link.
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &kEnable, sizeof(kEnable)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVHOPLIMIT (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_UNICAST_HOPS (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException",
- "failed to create the setsockopt IPV6_MULTICAST_HOPS (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifName.c_str(), strlen(ifName.c_str()))) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt SO_BINDTODEVICE (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
return sock;
}
@@ -129,8 +76,8 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- {"nativeCreateIcmp6Socket", "(Ljava/lang/String;)I",
- (void *)com_android_server_thread_InfraInterfaceController_createIcmp6Socket},
+ {"nativeCreateFilteredIcmp6Socket", "()I",
+ (void *)com_android_server_thread_InfraInterfaceController_createFilteredIcmp6Socket},
};
int register_com_android_server_thread_InfraInterfaceController(JNIEnv *env) {
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 36ce4d5..41f34ff 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -17,6 +17,12 @@
package android.net.thread.cts;
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
+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_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
@@ -37,10 +43,8 @@
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;
-import static org.junit.Assume.assumeNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -60,6 +64,8 @@
import android.net.thread.ThreadNetworkException;
import android.net.thread.ThreadNetworkManager;
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;
@@ -69,16 +75,13 @@
import androidx.test.filters.LargeTest;
import com.android.net.module.util.ArrayTrackRecord;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.FunctionalUtils.ThrowingRunnable;
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 java.nio.charset.StandardCharsets;
import java.time.Duration;
@@ -99,8 +102,7 @@
/** CTS tests for {@link ThreadNetworkController}. */
@LargeTest
-@RunWith(DevSdkIgnoreRunner.class)
-@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) // Thread is available on only U+
+@RequiresThreadFeature
public class ThreadNetworkControllerTest {
private static final int JOIN_TIMEOUT_MILLIS = 30 * 1000;
private static final int LEAVE_TIMEOUT_MILLIS = 2_000;
@@ -109,11 +111,12 @@
private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 30_000;
+ private static final int SERVICE_LOST_TIMEOUT_MILLIS = 20_000;
private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
private static final String THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
- @Rule public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private ExecutorService mExecutor;
@@ -126,14 +129,10 @@
@Before
public void setUp() throws Exception {
- ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
- if (manager != null) {
- mController = manager.getAllThreadNetworkControllers().get(0);
- }
-
- // TODO: we will also need it in tearDown(), it's better to have a Rule to skip
- // tests if a feature is not available.
- assumeNotNull(mController);
+ mController =
+ mContext.getSystemService(ThreadNetworkManager.class)
+ .getAllThreadNetworkControllers()
+ .get(0);
mGrantedPermissions = new HashSet<String>();
mExecutor = Executors.newSingleThreadExecutor();
@@ -146,9 +145,6 @@
@After
public void tearDown() throws Exception {
- if (mController == null) {
- return;
- }
dropAllPermissions();
leaveAndWait(mController);
tearDownTestNetwork();
@@ -177,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<>();
@@ -754,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<>();
@@ -797,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
@@ -816,6 +816,20 @@
assertThat(isAttached(mController)).isTrue();
assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
+ NetworkCapabilities caps =
+ runAsShell(
+ ACCESS_NETWORK_STATE, () -> cm.getNetworkCapabilities(networkFuture.get()));
+ assertThat(caps).isNotNull();
+ assertThat(caps.hasTransport(NetworkCapabilities.TRANSPORT_THREAD)).isTrue();
+ assertThat(caps.getCapabilities())
+ .asList()
+ .containsAtLeast(
+ NET_CAPABILITY_LOCAL_NETWORK,
+ NET_CAPABILITY_NOT_METERED,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_NOT_VCN_MANAGED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_TRUSTED);
}
private void grantPermissions(String... permissions) {
@@ -845,9 +859,11 @@
assertThat(txtMap.get("rv")).isNotNull();
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
+ assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
+ @Ignore("b/333649897: Enable this when it's not flaky at all")
public void meshcopService_joinedNetwork_discoveredHasNetwork() throws Exception {
setUpTestNetwork();
@@ -870,26 +886,29 @@
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
assertThat(txtMap.get("id").length).isEqualTo(16);
+ assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
public void meshcopService_threadDisabled_notDiscovered() throws Exception {
setUpTestNetwork();
-
CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
NsdManager.DiscoveryListener listener =
discoverForServiceLost(MESHCOP_SERVICE_TYPE, serviceLostFuture);
+
setEnabledAndWait(mController, false);
+
try {
- serviceLostFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
+ serviceLostFuture.get(SERVICE_LOST_TIMEOUT_MILLIS, MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException ignored) {
// It's fine if the service lost event didn't show up. The service may not ever be
// advertised.
} finally {
mNsdManager.stopServiceDiscovery(listener);
}
-
- assertThrows(TimeoutException.class, () -> discoverService(MESHCOP_SERVICE_TYPE));
+ assertThrows(
+ TimeoutException.class,
+ () -> discoverService(MESHCOP_SERVICE_TYPE, SERVICE_LOST_TIMEOUT_MILLIS));
}
private static void dropAllPermissions() {
@@ -1009,7 +1028,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() {
@@ -1107,6 +1130,12 @@
// Return the first discovered service instance.
private NsdServiceInfo discoverService(String serviceType) throws Exception {
+ return discoverService(serviceType, SERVICE_DISCOVERY_TIMEOUT_MILLIS);
+ }
+
+ // Return the first discovered service instance.
+ private NsdServiceInfo discoverService(String serviceType, int timeoutMilliseconds)
+ throws Exception {
CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
NsdManager.DiscoveryListener listener =
new DefaultDiscoveryListener() {
@@ -1115,9 +1144,14 @@
serviceInfoFuture.complete(serviceInfo);
}
};
- mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ mNsdManager.discoverServices(
+ serviceType,
+ NsdManager.PROTOCOL_DNS_SD,
+ mTestNetworkTracker.getNetwork(),
+ mExecutor,
+ listener);
try {
- serviceInfoFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
+ serviceInfoFuture.get(timeoutMilliseconds, MILLISECONDS);
} finally {
mNsdManager.stopServiceDiscovery(listener);
}
@@ -1134,7 +1168,12 @@
serviceInfoFuture.complete(serviceInfo);
}
};
- mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ mNsdManager.discoverServices(
+ serviceType,
+ NsdManager.PROTOCOL_DNS_SD,
+ mTestNetworkTracker.getNetwork(),
+ mExecutor,
+ listener);
return listener;
}
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 6ba192d..71693af 100644
--- a/thread/tests/integration/Android.bp
+++ b/thread/tests/integration/Android.bp
@@ -30,8 +30,11 @@
"net-tests-utils",
"net-utils-device-common",
"net-utils-device-common-bpf",
+ "net-utils-device-common-struct-base",
"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 e8ef346..8c63d37 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -17,14 +17,11 @@
package android.net.thread;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
-import static android.Manifest.permission.NETWORK_SETTINGS;
-import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static android.net.thread.utils.IntegrationTestUtils.JOIN_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.isExpectedIcmpv6Packet;
import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
-import static android.net.thread.utils.IntegrationTestUtils.isSimulatedThreadRadioSupported;
import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination;
import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
import static android.net.thread.utils.IntegrationTestUtils.pollForPacket;
@@ -33,30 +30,36 @@
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assume.assumeNotNull;
-import static org.junit.Assume.assumeTrue;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.Objects.requireNonNull;
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;
import android.net.thread.utils.InfraNetworkDevice;
+import android.net.thread.utils.OtDaemonController;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresIpv6MulticastRouting;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
+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;
@@ -67,6 +70,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -74,27 +78,16 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/** Integration test cases for Thread Border Routing feature. */
@RunWith(AndroidJUnit4.class)
+@RequiresThreadFeature
+@RequiresSimulationThreadDevice
@LargeTest
public class BorderRoutingTest {
private static final String TAG = BorderRoutingTest.class.getSimpleName();
- private final Context mContext = ApplicationProvider.getApplicationContext();
- private ThreadNetworkController mController;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private TestNetworkTracker mInfraNetworkTracker;
- private List<FullThreadDevice> mFtds;
- private TapPacketReader mInfraNetworkReader;
- private InfraNetworkDevice mInfraDevice;
-
private static final int NUM_FTD = 2;
- private static final String KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED = "5.15.0";
private static final Inet6Address GROUP_ADDR_SCOPE_5 =
(Inet6Address) InetAddresses.parseNumericAddress("ff05::1234");
private static final Inet6Address GROUP_ADDR_SCOPE_4 =
@@ -113,16 +106,24 @@
private static final ActiveOperationalDataset DEFAULT_DATASET =
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final ThreadNetworkControllerWrapper mController =
+ ThreadNetworkControllerWrapper.newInstance(mContext);
+ private OtDaemonController mOtCtl;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private TestNetworkTracker mInfraNetworkTracker;
+ private List<FullThreadDevice> mFtds;
+ private TapPacketReader mInfraNetworkReader;
+ private InfraNetworkDevice mInfraDevice;
+
@Before
public void setUp() throws Exception {
- assumeTrue(isSimulatedThreadRadioSupported());
- final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
- if (manager != null) {
- mController = manager.getAllThreadNetworkControllers().get(0);
- }
-
- // Run the tests on only devices where the Thread feature is available
- assumeNotNull(mController);
+ // TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
+ mOtCtl = new OtDaemonController();
+ mOtCtl.factoryReset();
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
@@ -130,13 +131,12 @@
mFtds = new ArrayList<>();
setUpInfraNetwork();
-
- // BR forms a network.
- startBrLeader();
+ mController.setEnabledAndWait(true);
+ mController.joinAndWait(DEFAULT_DATASET);
// Creates a infra network device.
mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
- startInfraDevice();
+ startInfraDeviceAndWaitForOnLinkAddr();
// Create Ftds
for (int i = 0; i < NUM_FTD; ++i) {
@@ -146,20 +146,8 @@
@After
public void tearDown() throws Exception {
- if (mController == null) {
- return;
- }
-
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- NETWORK_SETTINGS,
- () -> {
- CountDownLatch latch = new CountDownLatch(2);
- mController.setTestNetworkAsUpstream(
- null, directExecutor(), v -> latch.countDown());
- mController.leave(directExecutor(), v -> latch.countDown());
- latch.await(10, TimeUnit.SECONDS);
- });
+ mController.setTestNetworkAsUpstreamAndWait(null);
+ mController.leaveAndWait();
tearDownInfraNetwork();
mHandlerThread.quitSafely();
@@ -172,7 +160,7 @@
}
@Test
- public void unicastRouting_infraDevicePingTheadDeviceOmr_replyReceived() throws Exception {
+ public void unicastRouting_infraDevicePingThreadDeviceOmr_replyReceived() throws Exception {
/*
* <pre>
* Topology:
@@ -182,22 +170,69 @@
* </pre>
*/
- // Let ftd join the network.
FullThreadDevice ftd = mFtds.get(0);
startFtdChild(ftd);
- // Infra device sends an echo request to FTD's OMR.
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
// Infra device receives an echo reply sent by FTD.
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, null /* srcAddress */));
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ }
+
+ @Test
+ public void unicastRouting_afterFactoryResetInfraDevicePingThreadDeviceOmr_replyReceived()
+ throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ startInfraDeviceAndWaitForOnLinkAddr();
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+
+ mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
+
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ }
+
+ @Test
+ public void unicastRouting_afterInfraNetworkSwitchInfraDevicePingThreadDeviceOmr_replyReceived()
+ throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+ Inet6Address ftdOmr = ftd.getOmrAddress();
+ // Create a new infra network and let Thread prefer it
+ TestNetworkTracker oldInfraNetworkTracker = mInfraNetworkTracker;
+ try {
+ setUpInfraNetwork();
+ mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
+ startInfraDeviceAndWaitForOnLinkAddr();
+
+ mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
+
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
+ } finally {
+ runAsShell(MANAGE_TEST_NETWORKS, () -> oldInfraNetworkTracker.teardown());
+ }
}
@Test
public void unicastRouting_borderRouterSendsUdpToThreadDevice_datagramReceived()
throws Exception {
- assumeTrue(isSimulatedThreadRadioSupported());
-
/*
* <pre>
* Topology:
@@ -207,19 +242,10 @@
* </pre>
*/
- // BR forms a network.
- CompletableFuture<Void> joinFuture = new CompletableFuture<>();
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
- joinFuture.get(RESTART_JOIN_TIMEOUT.toMillis(), MILLISECONDS);
-
- // Creates a Full Thread Device (FTD) and lets it join the network.
FullThreadDevice ftd = mFtds.get(0);
startFtdChild(ftd);
- Inet6Address ftdOmr = ftd.getOmrAddress();
- Inet6Address ftdMlEid = ftd.getMlEid();
- assertNotNull(ftdMlEid);
+ Inet6Address ftdOmr = requireNonNull(ftd.getOmrAddress());
+ Inet6Address ftdMlEid = requireNonNull(ftd.getMlEid());
ftd.udpBind(ftdOmr, 12345);
sendUdpMessage(ftdOmr, 12345, "aaaaaaaa");
@@ -231,9 +257,24 @@
}
@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 {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -252,10 +293,10 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void
multicastRouting_ftdSubscribedScope3MulticastAddress_infraLinkNotJoinMulticastGroup()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -274,9 +315,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_ftdSubscribedMulticastAddress_canPingfromInfraLink()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -296,9 +337,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_inboundForwarding_afterBrRejoinFtdRepliesSubscribedAddress()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
// TODO (b/327311034): Testing bbr state switch from primary mode to secondary mode and back
// to primary mode requires an additional BR in the Thread network. This is not currently
@@ -306,9 +347,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_ftdSubscribedScope3MulticastAddress_cannotPingfromInfraLink()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -328,9 +369,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_ftdNotSubscribedMulticastAddress_cannotPingFromInfraDevice()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -349,9 +390,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_multipleFtdsSubscribedDifferentAddresses_canPingFromInfraDevice()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -385,9 +426,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_multipleFtdsSubscribedSameAddress_canPingFromInfraDevice()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -420,8 +461,8 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_outboundForwarding_scopeLargerThan3IsForwarded() throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -445,9 +486,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_outboundForwarding_scopeSmallerThan4IsNotForwarded()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -468,8 +509,8 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_outboundForwarding_llaToScope4IsNotForwarded() throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -484,15 +525,15 @@
Inet6Address ftdLla = ftd.getLinkLocalAddress();
assertNotNull(ftdLla);
- ftd.ping(GROUP_ADDR_SCOPE_4, ftdLla, 100 /* size */, 1 /* count */);
+ ftd.ping(GROUP_ADDR_SCOPE_4, ftdLla);
assertNull(
pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdLla, GROUP_ADDR_SCOPE_4));
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_outboundForwarding_mlaToScope4IsNotForwarded() throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -508,7 +549,7 @@
assertFalse(ftdMlas.isEmpty());
for (Inet6Address ftdMla : ftdMlas) {
- ftd.ping(GROUP_ADDR_SCOPE_4, ftdMla, 100 /* size */, 1 /* count */);
+ ftd.ping(GROUP_ADDR_SCOPE_4, ftdMla);
assertNull(
pollForPacketOnInfraNetwork(
@@ -517,9 +558,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_infraNetworkSwitch_ftdRepliesToSubscribedAddress()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -538,7 +579,7 @@
tearDownInfraNetwork();
setUpInfraNetwork();
mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
- startInfraDevice();
+ startInfraDeviceAndWaitForOnLinkAddr();
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
@@ -546,8 +587,8 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_infraNetworkSwitch_outboundPacketIsForwarded() throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
/*
* <pre>
* Topology:
@@ -565,49 +606,29 @@
tearDownInfraNetwork();
setUpInfraNetwork();
mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
- startInfraDevice();
+ startInfraDeviceAndWaitForOnLinkAddr();
- ftd.ping(GROUP_ADDR_SCOPE_5);
ftd.ping(GROUP_ADDR_SCOPE_4);
assertNotNull(
- pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5));
- assertNotNull(
pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
}
- private void setUpInfraNetwork() {
+ private void setUpInfraNetwork() throws Exception {
mInfraNetworkTracker =
runAsShell(
MANAGE_TEST_NETWORKS,
() ->
initTestNetwork(
mContext, new LinkProperties(), 5000 /* timeoutMs */));
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- NETWORK_SETTINGS,
- () -> {
- CompletableFuture<Void> future = new CompletableFuture<>();
- mController.setTestNetworkAsUpstream(
- mInfraNetworkTracker.getTestIface().getInterfaceName(),
- directExecutor(),
- future::complete);
- future.get(5, TimeUnit.SECONDS);
- });
+ mController.setTestNetworkAsUpstreamAndWait(
+ mInfraNetworkTracker.getTestIface().getInterfaceName());
}
private void tearDownInfraNetwork() {
runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown());
}
- private void startBrLeader() throws Exception {
- CompletableFuture<Void> joinFuture = new CompletableFuture<>();
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
- joinFuture.get(RESTART_JOIN_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
- }
-
private void startFtdChild(FullThreadDevice ftd) throws Exception {
ftd.factoryReset();
ftd.joinNetwork(DEFAULT_DATASET);
@@ -617,7 +638,7 @@
assertNotNull(ftdOmr);
}
- private void startInfraDevice() throws Exception {
+ private void startInfraDeviceAndWaitForOnLinkAddr() throws Exception {
mInfraDevice =
new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), mInfraNetworkReader);
mInfraDevice.runSlaac(Duration.ofSeconds(60));
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
new file mode 100644
index 0000000..e10f134
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -0,0 +1,502 @@
+/*
+ * 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 android.net.thread;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
+import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.discoverForServiceLost;
+import static android.net.thread.utils.IntegrationTestUtils.discoverService;
+import static android.net.thread.utils.IntegrationTestUtils.resolveService;
+import static android.net.thread.utils.IntegrationTestUtils.resolveServiceUntil;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.net.thread.utils.FullThreadDevice;
+import android.net.thread.utils.OtDaemonController;
+import android.net.thread.utils.TapTestNetworkTracker;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
+import android.net.thread.utils.ThreadNetworkControllerWrapper;
+import android.os.HandlerThread;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Correspondence;
+
+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 java.net.Inet6Address;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/** Integration test cases for Service Discovery feature. */
+@RunWith(AndroidJUnit4.class)
+@RequiresThreadFeature
+@RequiresSimulationThreadDevice
+@LargeTest
+public class ServiceDiscoveryTest {
+ private static final String TAG = ServiceDiscoveryTest.class.getSimpleName();
+ private static final int NUM_FTD = 3;
+
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
+ private static final byte[] DEFAULT_DATASET_TLVS =
+ base16().decode(
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8");
+ private static final ActiveOperationalDataset DEFAULT_DATASET =
+ ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+
+ private static final Correspondence<byte[], byte[]> BYTE_ARRAY_EQUALITY =
+ Correspondence.from(Arrays::equals, "is equivalent to");
+
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final ThreadNetworkControllerWrapper mController =
+ ThreadNetworkControllerWrapper.newInstance(mContext);
+ private final OtDaemonController mOtCtl = new OtDaemonController();
+ private HandlerThread mHandlerThread;
+ private NsdManager mNsdManager;
+ private TapTestNetworkTracker mTestNetworkTracker;
+ private final List<FullThreadDevice> mFtds = new ArrayList<>();
+ private final List<RegistrationListener> mRegistrationListeners = new ArrayList<>();
+
+ @Before
+ public void setUp() throws Exception {
+ mOtCtl.factoryReset();
+ mController.setEnabledAndWait(true);
+ mController.joinAndWait(DEFAULT_DATASET);
+ mNsdManager = mContext.getSystemService(NsdManager.class);
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+
+ mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
+ assertThat(mTestNetworkTracker).isNotNull();
+ mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName());
+
+ // Create the FTDs in setUp() so that the FTDs can be safely released in tearDown().
+ // Don't create new FTDs in test cases.
+ for (int i = 0; i < NUM_FTD; ++i) {
+ FullThreadDevice ftd = new FullThreadDevice(10 + i /* node ID */);
+ ftd.autoStartSrpClient();
+ mFtds.add(ftd);
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ for (RegistrationListener listener : mRegistrationListeners) {
+ unregisterService(listener);
+ }
+ for (FullThreadDevice ftd : mFtds) {
+ // Clear registered SRP hosts and services
+ if (ftd.isSrpHostRegistered()) {
+ ftd.removeSrpHost();
+ }
+ ftd.destroy();
+ }
+ if (mTestNetworkTracker != null) {
+ mTestNetworkTracker.tearDown();
+ }
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread.join();
+ }
+ mController.setTestNetworkAsUpstreamAndWait(null);
+ mController.leaveAndWait();
+ }
+
+ @Test
+ public void advertisingProxy_multipleSrpClientsRegisterServices_servicesResolvableByMdns()
+ throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * Thread
+ * Border Router -------------- Full Thread device 1
+ * (Cuttlefish) |
+ * +------ Full Thread device 2
+ * |
+ * +------ Full Thread device 3
+ * </pre>
+ */
+
+ // Creates Full Thread Devices (FTD) and let them join the network.
+ for (FullThreadDevice ftd : mFtds) {
+ ftd.joinNetwork(DEFAULT_DATASET);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ }
+
+ int randomId = new Random().nextInt(10_000);
+
+ String serviceNamePrefix = "service-" + randomId + "-";
+ String serviceTypePrefix = "_test" + randomId;
+ String hostnamePrefix = "host-" + randomId + "-";
+
+ // For every FTD, let it register an SRP service.
+ for (int i = 0; i < mFtds.size(); ++i) {
+ FullThreadDevice ftd = mFtds.get(i);
+ ftd.setSrpHostname(hostnamePrefix + i);
+ ftd.setSrpHostAddresses(List.of(ftd.getOmrAddress(), ftd.getMlEid()));
+ ftd.addSrpService(
+ serviceNamePrefix + i,
+ serviceTypePrefix + i + "._tcp",
+ List.of("_sub1", "_sub2"),
+ 12345 /* port */,
+ Map.of("key1", bytes(0x01, 0x02), "key2", bytes(i)));
+ }
+
+ // Check the advertised services are discoverable and resolvable by NsdManager
+ for (int i = 0; i < mFtds.size(); ++i) {
+ NsdServiceInfo discoveredService =
+ discoverService(mNsdManager, serviceTypePrefix + i + "._tcp");
+ assertThat(discoveredService).isNotNull();
+ NsdServiceInfo resolvedService = resolveService(mNsdManager, discoveredService);
+ assertThat(resolvedService.getServiceName()).isEqualTo(serviceNamePrefix + i);
+ assertThat(resolvedService.getServiceType()).isEqualTo(serviceTypePrefix + i + "._tcp");
+ assertThat(resolvedService.getPort()).isEqualTo(12345);
+ assertThat(resolvedService.getAttributes())
+ .comparingValuesUsing(BYTE_ARRAY_EQUALITY)
+ .containsExactly("key1", bytes(0x01, 0x02), "key2", bytes(i));
+ assertThat(resolvedService.getHostname()).isEqualTo(hostnamePrefix + i);
+ assertThat(resolvedService.getHostAddresses())
+ .containsExactly(mFtds.get(i).getOmrAddress());
+ }
+ }
+
+ @Test
+ public void advertisingProxy_srpClientUpdatesService_updatedServiceResolvableByMdns()
+ throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * Thread
+ * Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ // Creates a Full Thread Devices (FTD) and let it join the network.
+ FullThreadDevice ftd = mFtds.get(0);
+ ftd.joinNetwork(DEFAULT_DATASET);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ ftd.setSrpHostname("my-host");
+ ftd.setSrpHostAddresses(List.of((Inet6Address) parseNumericAddress("2001:db8::1")));
+ ftd.addSrpService(
+ "my-service",
+ "_test._tcp",
+ Collections.emptyList() /* subtypes */,
+ 12345 /* port */,
+ Map.of("key1", bytes(0x01, 0x02), "key2", bytes(0x03)));
+
+ // Update the host addresses
+ ftd.setSrpHostAddresses(
+ List.of(
+ (Inet6Address) parseNumericAddress("2001:db8::1"),
+ (Inet6Address) parseNumericAddress("2001:db8::2")));
+ // Update the service
+ ftd.updateSrpService(
+ "my-service", "_test._tcp", List.of("_sub3"), 11111, Map.of("key1", bytes(0x04)));
+ waitFor(ftd::isSrpHostRegistered, SERVICE_DISCOVERY_TIMEOUT);
+
+ // Check the advertised service is discoverable and resolvable by NsdManager
+ NsdServiceInfo discoveredService = discoverService(mNsdManager, "_test._tcp");
+ assertThat(discoveredService).isNotNull();
+ NsdServiceInfo resolvedService =
+ resolveServiceUntil(
+ mNsdManager,
+ discoveredService,
+ s -> s.getPort() == 11111 && s.getHostAddresses().size() == 2);
+ assertThat(resolvedService.getServiceName()).isEqualTo("my-service");
+ assertThat(resolvedService.getServiceType()).isEqualTo("_test._tcp");
+ assertThat(resolvedService.getPort()).isEqualTo(11111);
+ assertThat(resolvedService.getAttributes())
+ .comparingValuesUsing(BYTE_ARRAY_EQUALITY)
+ .containsExactly("key1", bytes(0x04));
+ assertThat(resolvedService.getHostname()).isEqualTo("my-host");
+ assertThat(resolvedService.getHostAddresses())
+ .containsExactly(
+ parseNumericAddress("2001:db8::1"), parseNumericAddress("2001:db8::2"));
+ }
+
+ @Test
+ @Ignore("TODO: b/333806992 - Enable when it's not flaky at all")
+ public void advertisingProxy_srpClientUnregistersService_serviceIsNotDiscoverableByMdns()
+ throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * Thread
+ * Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ // Creates a Full Thread Devices (FTD) and let it join the network.
+ FullThreadDevice ftd = mFtds.get(0);
+ ftd.joinNetwork(DEFAULT_DATASET);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ ftd.setSrpHostname("my-host");
+ ftd.setSrpHostAddresses(
+ List.of(
+ (Inet6Address) parseNumericAddress("2001:db8::1"),
+ (Inet6Address) parseNumericAddress("2001:db8::2")));
+ ftd.addSrpService(
+ "my-service",
+ "_test._udp",
+ List.of("_sub1"),
+ 12345 /* port */,
+ Map.of("key1", bytes(0x01, 0x02), "key2", bytes(0x03)));
+ // Wait for the service to be discoverable by NsdManager.
+ assertThat(discoverService(mNsdManager, "_test._udp")).isNotNull();
+
+ // Unregister the service.
+ CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
+ NsdManager.DiscoveryListener listener =
+ discoverForServiceLost(mNsdManager, "_test._udp", serviceLostFuture);
+ ftd.removeSrpService("my-service", "_test._udp", true /* notifyServer */);
+
+ // Verify the service becomes lost.
+ try {
+ serviceLostFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+ } finally {
+ mNsdManager.stopServiceDiscovery(listener);
+ }
+ assertThrows(TimeoutException.class, () -> discoverService(mNsdManager, "_test._udp"));
+ }
+
+ @Test
+ public void meshcopOverlay_vendorAndModelNameAreSetToOverlayValue() throws Exception {
+ NsdServiceInfo discoveredService = discoverService(mNsdManager, "_meshcop._udp");
+ assertThat(discoveredService).isNotNull();
+ NsdServiceInfo meshcopService = resolveService(mNsdManager, discoveredService);
+
+ Map<String, byte[]> txtMap = meshcopService.getAttributes();
+ assertThat(txtMap.get("vn")).isEqualTo("Android".getBytes(UTF_8));
+ assertThat(txtMap.get("mn")).isEqualTo("Thread Border Router".getBytes(UTF_8));
+ }
+
+ @Test
+ @Ignore("TODO: b/332452386 - Enable this test case when it handles the multi-client case well")
+ public void discoveryProxy_multipleClientsBrowseAndResolveServiceOverMdns() throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * Thread
+ * Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ RegistrationListener listener = new RegistrationListener();
+ NsdServiceInfo info = new NsdServiceInfo();
+ info.setServiceType("_testservice._tcp");
+ info.setServiceName("test-service");
+ info.setPort(12345);
+ info.setHostname("testhost");
+ info.setHostAddresses(List.of(parseNumericAddress("2001::1")));
+ info.setAttribute("key1", bytes(0x01, 0x02));
+ info.setAttribute("key2", bytes(0x03));
+ registerService(info, listener);
+ mRegistrationListeners.add(listener);
+ for (int i = 0; i < NUM_FTD; ++i) {
+ FullThreadDevice ftd = mFtds.get(i);
+ ftd.joinNetwork(DEFAULT_DATASET);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ ftd.setDnsServerAddress(mOtCtl.getMlEid().getHostAddress());
+ }
+ final ArrayList<NsdServiceInfo> browsedServices = new ArrayList<>();
+ final ArrayList<NsdServiceInfo> resolvedServices = new ArrayList<>();
+ final ArrayList<Thread> threads = new ArrayList<>();
+ for (int i = 0; i < NUM_FTD; ++i) {
+ browsedServices.add(null);
+ resolvedServices.add(null);
+ }
+ for (int i = 0; i < NUM_FTD; ++i) {
+ final FullThreadDevice ftd = mFtds.get(i);
+ final int index = i;
+ Runnable task =
+ () -> {
+ browsedServices.set(
+ index,
+ ftd.browseService("_testservice._tcp.default.service.arpa."));
+ resolvedServices.set(
+ index,
+ ftd.resolveService(
+ "test-service", "_testservice._tcp.default.service.arpa."));
+ };
+ threads.add(new Thread(task));
+ }
+ for (Thread thread : threads) {
+ thread.start();
+ }
+ for (Thread thread : threads) {
+ thread.join();
+ }
+
+ for (int i = 0; i < NUM_FTD; ++i) {
+ NsdServiceInfo browsedService = browsedServices.get(i);
+ assertThat(browsedService.getServiceName()).isEqualTo("test-service");
+ assertThat(browsedService.getPort()).isEqualTo(12345);
+
+ NsdServiceInfo resolvedService = resolvedServices.get(i);
+ assertThat(resolvedService.getServiceName()).isEqualTo("test-service");
+ assertThat(resolvedService.getPort()).isEqualTo(12345);
+ assertThat(resolvedService.getHostname()).isEqualTo("testhost.default.service.arpa.");
+ assertThat(resolvedService.getHostAddresses())
+ .containsExactly(parseNumericAddress("2001::1"));
+ assertThat(resolvedService.getAttributes())
+ .comparingValuesUsing(BYTE_ARRAY_EQUALITY)
+ .containsExactly("key1", bytes(0x01, 0x02), "key2", bytes(3));
+ }
+ }
+
+ @Test
+ public void discoveryProxy_browseAndResolveServiceAtSrpServer() throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * Thread
+ * Border Router -------+------ SRP client
+ * (Cuttlefish) |
+ * +------ DNS client
+ *
+ * </pre>
+ */
+ FullThreadDevice srpClient = mFtds.get(0);
+ srpClient.joinNetwork(DEFAULT_DATASET);
+ srpClient.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ srpClient.setSrpHostname("my-host");
+ srpClient.setSrpHostAddresses(List.of((Inet6Address) parseNumericAddress("2001::1")));
+ srpClient.addSrpService(
+ "my-service",
+ "_test._udp",
+ List.of("_sub1"),
+ 12345 /* port */,
+ Map.of("key1", bytes(0x01, 0x02), "key2", bytes(0x03)));
+
+ FullThreadDevice dnsClient = mFtds.get(1);
+ dnsClient.joinNetwork(DEFAULT_DATASET);
+ dnsClient.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ dnsClient.setDnsServerAddress(mOtCtl.getMlEid().getHostAddress());
+
+ NsdServiceInfo browsedService = dnsClient.browseService("_test._udp.default.service.arpa.");
+ assertThat(browsedService.getServiceName()).isEqualTo("my-service");
+ assertThat(browsedService.getPort()).isEqualTo(12345);
+ assertThat(browsedService.getHostname()).isEqualTo("my-host.default.service.arpa.");
+ assertThat(browsedService.getHostAddresses())
+ .containsExactly(parseNumericAddress("2001::1"));
+ assertThat(browsedService.getAttributes())
+ .comparingValuesUsing(BYTE_ARRAY_EQUALITY)
+ .containsExactly("key1", bytes(0x01, 0x02), "key2", bytes(3));
+
+ NsdServiceInfo resolvedService =
+ dnsClient.resolveService("my-service", "_test._udp.default.service.arpa.");
+ assertThat(resolvedService.getServiceName()).isEqualTo("my-service");
+ assertThat(resolvedService.getPort()).isEqualTo(12345);
+ assertThat(resolvedService.getHostname()).isEqualTo("my-host.default.service.arpa.");
+ assertThat(resolvedService.getHostAddresses())
+ .containsExactly(parseNumericAddress("2001::1"));
+ assertThat(resolvedService.getAttributes())
+ .comparingValuesUsing(BYTE_ARRAY_EQUALITY)
+ .containsExactly("key1", bytes(0x01, 0x02), "key2", bytes(3));
+ }
+
+ private void registerService(NsdServiceInfo serviceInfo, RegistrationListener listener)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ mNsdManager.registerService(serviceInfo, PROTOCOL_DNS_SD, listener);
+ listener.waitForRegistered();
+ }
+
+ private void unregisterService(RegistrationListener listener)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ mNsdManager.unregisterService(listener);
+ listener.waitForUnregistered();
+ }
+
+ private static class RegistrationListener implements NsdManager.RegistrationListener {
+ private final CompletableFuture<Void> mRegisteredFuture = new CompletableFuture<>();
+ private final CompletableFuture<Void> mUnRegisteredFuture = new CompletableFuture<>();
+
+ RegistrationListener() {}
+
+ @Override
+ public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {}
+
+ @Override
+ public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {}
+
+ @Override
+ public void onServiceRegistered(NsdServiceInfo serviceInfo) {
+ mRegisteredFuture.complete(null);
+ }
+
+ @Override
+ public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
+ mUnRegisteredFuture.complete(null);
+ }
+
+ public void waitForRegistered()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ mRegisteredFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+ }
+
+ public void waitForUnregistered()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ mUnRegisteredFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+ }
+ }
+
+ private static byte[] bytes(int... byteInts) {
+ byte[] bytes = new byte[byteInts.length];
+ for (int i = 0; i < byteInts.length; ++i) {
+ bytes[i] = (byte) byteInts[i];
+ }
+ return bytes;
+ }
+}
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 70897f0..61b6eac 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -16,31 +16,36 @@
package android.net.thread;
-import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
-import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
-import static android.net.thread.utils.IntegrationTestUtils.LEAVE_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
-import static android.net.thread.utils.IntegrationTestUtils.waitForStateAnyOf;
+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.testutils.TestPermissionUtil.runAsShell;
+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.util.concurrent.MoreExecutors.directExecutor;
+import static com.google.common.truth.Truth.assertWithMessage;
-import static org.junit.Assume.assumeNotNull;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import android.annotation.Nullable;
import android.content.Context;
-import android.net.thread.ThreadNetworkController.StateCallback;
+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;
+import android.net.thread.utils.ThreadNetworkControllerWrapper;
import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider;
@@ -49,20 +54,34 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
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.CompletableFuture;
+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 {
- private final Context mContext = ApplicationProvider.getApplicationContext();
- private ThreadNetworkController mController;
- private OtDaemonController mOtCtl;
+ // 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 =
@@ -75,124 +94,276 @@
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 {
- final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
- if (manager != null) {
- mController = manager.getAllThreadNetworkControllers().get(0);
- }
-
- // Run the tests on only devices where the Thread feature is available
- assumeNotNull(mController);
-
+ mExecutor = Executors.newSingleThreadExecutor();
mOtCtl = new OtDaemonController();
- leaveAndWait(mController);
+ 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 {
- if (mController == null) {
- return;
- }
+ mController.setTestNetworkAsUpstreamAndWait(null);
+ mController.leaveAndWait();
- setTestUpStreamNetworkAndWait(mController, null);
- leaveAndWait(mController);
+ mFtd.destroy();
+ mExecutor.shutdownNow();
}
@Test
public void otDaemonRestart_notJoinedAndStopped_deviceRoleIsStopped() throws Exception {
- leaveAndWait(mController);
+ mController.leaveAndWait();
runShellCommand("stop ot-daemon");
// TODO(b/323331973): the sleep is needed to workaround the race conditions
SystemClock.sleep(200);
- waitForStateAnyOf(mController, List.of(DEVICE_ROLE_STOPPED), CALLBACK_TIMEOUT);
+ mController.waitForRole(DEVICE_ROLE_STOPPED, CALLBACK_TIMEOUT);
}
@Test
- public void otDaemonRestart_JoinedNetworkAndStopped_autoRejoined() throws Exception {
- joinAndWait(mController, DEFAULT_DATASET);
+ public void otDaemonRestart_JoinedNetworkAndStopped_autoRejoinedAndTunIfStateConsistent()
+ throws Exception {
+ mController.joinAndWait(DEFAULT_DATASET);
runShellCommand("stop ot-daemon");
- waitForStateAnyOf(mController, List.of(DEVICE_ROLE_DETACHED), CALLBACK_TIMEOUT);
- waitForStateAnyOf(mController, List.of(DEVICE_ROLE_LEADER), RESTART_JOIN_TIMEOUT);
+ mController.waitForRole(DEVICE_ROLE_DETACHED, CALLBACK_TIMEOUT);
+ mController.waitForRole(DEVICE_ROLE_LEADER, RESTART_JOIN_TIMEOUT);
+ assertThat(mOtCtl.isInterfaceUp()).isTrue();
+ assertThat(runShellCommand("ifconfig thread-wpan")).contains("UP POINTOPOINT RUNNING");
}
@Test
public void otDaemonFactoryReset_deviceRoleIsStopped() throws Exception {
- joinAndWait(mController, DEFAULT_DATASET);
+ mController.joinAndWait(DEFAULT_DATASET);
mOtCtl.factoryReset();
- assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ assertThat(mController.getDeviceRole()).isEqualTo(DEVICE_ROLE_STOPPED);
}
@Test
public void otDaemonFactoryReset_addressesRemoved() throws Exception {
- joinAndWait(mController, DEFAULT_DATASET);
+ mController.joinAndWait(DEFAULT_DATASET);
mOtCtl.factoryReset();
- String ifconfig = runShellCommand("ifconfig thread-wpan");
+ String ifconfig = runShellCommand("ifconfig thread-wpan");
assertThat(ifconfig).doesNotContain("inet6 addr");
}
+ // TODO (b/323300829): add test for removing an OT address
@Test
- public void tunInterface_joinedNetwork_otAddressesAddedToTunInterface() throws Exception {
- joinAndWait(mController, DEFAULT_DATASET);
+ 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
+ public void otDaemonRestart_latestCountryCodeIsSetToOtDaemon() throws Exception {
+ runThreadCommand("force-country-code enabled CN");
+
+ runShellCommand("stop ot-daemon");
+ // TODO(b/323331973): the sleep is needed to workaround the race conditions
+ SystemClock.sleep(200);
+ mController.waitForRole(DEVICE_ROLE_STOPPED, CALLBACK_TIMEOUT);
+
+ assertThat(mOtCtl.getCountryCode()).isEqualTo("CN");
+ }
+
+ @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 int getDeviceRole(ThreadNetworkController controller) throws Exception {
- CompletableFuture<Integer> future = new CompletableFuture<>();
- StateCallback callback = future::complete;
- controller.registerStateCallback(directExecutor(), callback);
- try {
- return future.get(CALLBACK_TIMEOUT.toMillis(), MILLISECONDS);
- } finally {
- controller.unregisterStateCallback(callback);
+ 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 static void joinAndWait(
- ThreadNetworkController controller, ActiveOperationalDataset activeDataset)
- throws Exception {
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> controller.join(activeDataset, directExecutor(), result -> {}));
- waitForStateAnyOf(controller, List.of(DEVICE_ROLE_LEADER), RESTART_JOIN_TIMEOUT);
- }
-
- private static void leaveAndWait(ThreadNetworkController controller) throws Exception {
- CompletableFuture<Void> future = new CompletableFuture<>();
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> controller.leave(directExecutor(), future::complete));
- future.get(LEAVE_TIMEOUT.toMillis(), MILLISECONDS);
- }
-
- private static void setTestUpStreamNetworkAndWait(
- ThreadNetworkController controller, @Nullable String networkInterfaceName)
- throws Exception {
- CompletableFuture<Void> future = new CompletableFuture<>();
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- NETWORK_SETTINGS,
- () -> {
- controller.setTestNetworkAsUpstream(
- networkInterfaceName, directExecutor(), future::complete);
- });
- future.get(CALLBACK_TIMEOUT.toMillis(), MILLISECONDS);
+ 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/ThreadNetworkControllerTest.java b/thread/tests/integration/src/android/net/thread/ThreadNetworkControllerTest.java
new file mode 100644
index 0000000..ba04348
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/ThreadNetworkControllerTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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 android.net.thread;
+
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_OPERATION;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
+import android.os.OutcomeReceiver;
+import android.util.SparseIntArray;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** Tests for hide methods of {@link ThreadNetworkController}. */
+@LargeTest
+@RequiresThreadFeature
+@RunWith(AndroidJUnit4.class)
+public class ThreadNetworkControllerTest {
+ private static final int VALID_POWER = 32_767;
+ private static final int INVALID_POWER = 32_768;
+ private static final int VALID_CHANNEL = 20;
+ private static final int INVALID_CHANNEL = 10;
+ private static final String THREAD_NETWORK_PRIVILEGED =
+ "android.permission.THREAD_NETWORK_PRIVILEGED";
+
+ private static final SparseIntArray CHANNEL_MAX_POWERS =
+ new SparseIntArray() {
+ {
+ put(20, 32767);
+ }
+ };
+
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ExecutorService mExecutor;
+ private ThreadNetworkController mController;
+
+ @Before
+ public void setUp() throws Exception {
+ mController =
+ mContext.getSystemService(ThreadNetworkManager.class)
+ .getAllThreadNetworkControllers()
+ .get(0);
+
+ mExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ dropAllPermissions();
+ }
+
+ @Test
+ public void setChannelMaxPowers_withPrivilegedPermission_success() throws Exception {
+ CompletableFuture<Void> powerFuture = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ mController.setChannelMaxPowers(
+ CHANNEL_MAX_POWERS, mExecutor, newOutcomeReceiver(powerFuture)));
+
+ try {
+ assertThat(powerFuture.get()).isNull();
+ } catch (ExecutionException exception) {
+ ThreadNetworkException thrown = (ThreadNetworkException) exception.getCause();
+ assertThat(thrown.getErrorCode()).isEqualTo(ERROR_UNSUPPORTED_OPERATION);
+ }
+ }
+
+ @Test
+ public void setChannelMaxPowers_withoutPrivilegedPermission_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+
+ assertThrows(
+ SecurityException.class,
+ () -> mController.setChannelMaxPowers(CHANNEL_MAX_POWERS, mExecutor, v -> {}));
+ }
+
+ @Test
+ public void setChannelMaxPowers_emptyChannelMaxPower_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.setChannelMaxPowers(new SparseIntArray(), mExecutor, v -> {}));
+ }
+
+ @Test
+ public void setChannelMaxPowers_invalidChannel_throwsIllegalArgumentException() {
+ final SparseIntArray INVALID_CHANNEL_ARRAY =
+ new SparseIntArray() {
+ {
+ put(INVALID_CHANNEL, VALID_POWER);
+ }
+ };
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.setChannelMaxPowers(INVALID_CHANNEL_ARRAY, mExecutor, v -> {}));
+ }
+
+ @Test
+ public void setChannelMaxPowers_invalidPower_throwsIllegalArgumentException() {
+ final SparseIntArray INVALID_POWER_ARRAY =
+ new SparseIntArray() {
+ {
+ put(VALID_CHANNEL, INVALID_POWER);
+ }
+ };
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.setChannelMaxPowers(INVALID_POWER_ARRAY, mExecutor, v -> {}));
+ }
+
+ private static void dropAllPermissions() {
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
+ CompletableFuture<V> future) {
+ return new OutcomeReceiver<V, ThreadNetworkException>() {
+ @Override
+ public void onResult(V result) {
+ future.complete(result);
+ }
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ future.completeExceptionally(e);
+ }
+ };
+ }
+}
diff --git a/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java b/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
new file mode 100644
index 0000000..8835f40
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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 android.net.thread;
+
+import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
+import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
+import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
+import android.net.thread.utils.ThreadNetworkControllerWrapper;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ExecutionException;
+
+/** Integration tests for {@link ThreadNetworkShellCommand}. */
+@LargeTest
+@RequiresThreadFeature
+@RunWith(AndroidJUnit4.class)
+public class ThreadNetworkShellCommandTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final ThreadNetworkControllerWrapper mController =
+ ThreadNetworkControllerWrapper.newInstance(mContext);
+
+ @Before
+ public void setUp() {
+ ensureThreadEnabled();
+ }
+
+ @After
+ public void tearDown() {
+ ensureThreadEnabled();
+ }
+
+ private static void ensureThreadEnabled() {
+ runThreadCommand("force-stop-ot-daemon disabled");
+ runThreadCommand("enable");
+ }
+
+ @Test
+ public void enable_threadStateIsEnabled() throws Exception {
+ runThreadCommand("enable");
+
+ assertThat(mController.getEnabledState()).isEqualTo(STATE_ENABLED);
+ }
+
+ @Test
+ public void disable_threadStateIsDisabled() throws Exception {
+ runThreadCommand("disable");
+
+ assertThat(mController.getEnabledState()).isEqualTo(STATE_DISABLED);
+ }
+
+ @Test
+ public void forceStopOtDaemon_forceStopEnabled_otDaemonServiceDisappear() {
+ runThreadCommand("force-stop-ot-daemon enabled");
+
+ assertThat(runShellCommandOrThrow("service list")).doesNotContain("ot_daemon");
+ }
+
+ @Test
+ public void forceStopOtDaemon_forceStopEnabled_canNotEnableThread() throws Exception {
+ runThreadCommand("force-stop-ot-daemon enabled");
+
+ ExecutionException thrown =
+ assertThrows(ExecutionException.class, () -> mController.setEnabledAndWait(true));
+ ThreadNetworkException cause = (ThreadNetworkException) thrown.getCause();
+ assertThat(cause.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
+ }
+
+ @Test
+ public void forceStopOtDaemon_forceStopDisabled_otDaemonServiceAppears() throws Exception {
+ runThreadCommand("force-stop-ot-daemon disabled");
+
+ assertThat(runShellCommandOrThrow("service list")).contains("ot_daemon");
+ }
+
+ @Test
+ public void forceStopOtDaemon_forceStopDisabled_canEnableThread() throws Exception {
+ runThreadCommand("force-stop-ot-daemon disabled");
+
+ mController.setEnabledAndWait(true);
+ assertThat(mController.getEnabledState()).isEqualTo(STATE_ENABLED);
+ }
+
+ @Test
+ public void forceCountryCode_setCN_getCountryCodeReturnsCN() {
+ runThreadCommand("force-country-code enabled CN");
+
+ final String result = runThreadCommand("get-country-code");
+ assertThat(result).contains("Thread country code = CN");
+ }
+
+ private static String runThreadCommand(String cmd) {
+ return runShellCommandOrThrow("cmd thread_network " + cmd);
+ }
+}
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 6cb1675..c0a8eea 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -15,15 +15,21 @@
*/
package android.net.thread.utils;
+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;
@@ -31,9 +37,14 @@
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
import java.time.Duration;
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;
@@ -47,9 +58,21 @@
* available commands.
*/
public final class FullThreadDevice {
+ private static final int HOP_LIMIT = 64;
+ private static final int PING_INTERVAL = 1;
+ 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_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;
@@ -66,17 +89,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();
}
/**
@@ -191,19 +219,184 @@
public void udpBind(Inet6Address address, int port) {
udpClose();
udpOpen();
- executeCommand(String.format("udp bind %s %d", address.getHostAddress(), port));
+ executeCommand("udp bind %s %d", address.getHostAddress(), port);
}
/** Returns the message received on the UDP socket. */
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");
+ }
+
+ /** Sets the hostname (e.g. "MyHost") for the SRP client. */
+ public void setSrpHostname(String hostname) {
+ executeCommand("srp client host name " + hostname);
+ }
+
+ /** Sets the host addresses for the SRP client. */
+ public void setSrpHostAddresses(List<Inet6Address> addresses) {
+ executeCommand(
+ "srp client host address "
+ + String.join(
+ " ",
+ addresses.stream().map(Inet6Address::getHostAddress).toList()));
+ }
+
+ /** Removes the SRP host */
+ public void removeSrpHost() {
+ executeCommand("srp client host remove 1 1");
+ }
+
+ /**
+ * Adds an SRP service for the SRP client and wait for the registration to complete.
+ *
+ * @param serviceName the service name like "MyService"
+ * @param serviceType the service type like "_test._tcp"
+ * @param subtypes the service subtypes like "_sub1"
+ * @param port the port number in range [1, 65535]
+ * @param txtMap the map of TXT names and values
+ * @throws TimeoutException if the service isn't registered within timeout
+ */
+ public void addSrpService(
+ String serviceName,
+ String serviceType,
+ List<String> subtypes,
+ int port,
+ Map<String, byte[]> txtMap)
+ throws TimeoutException {
+ StringBuilder fullServiceType = new StringBuilder(serviceType);
+ for (String subtype : subtypes) {
+ fullServiceType.append(",").append(subtype);
+ }
+ executeCommand(
+ "srp client service add %s %s %d %d %d %s",
+ serviceName,
+ fullServiceType,
+ port,
+ 0 /* priority */,
+ 0 /* weight */,
+ txtMapToHexString(txtMap));
+ waitFor(() -> isSrpServiceRegistered(serviceName, serviceType), SERVICE_DISCOVERY_TIMEOUT);
+ }
+
+ /**
+ * Removes an SRP service for the SRP client.
+ *
+ * @param serviceName the service name like "MyService"
+ * @param serviceType the service type like "_test._tcp"
+ * @param notifyServer whether to notify SRP server about the removal
+ */
+ public void removeSrpService(String serviceName, String serviceType, boolean notifyServer) {
+ String verb = notifyServer ? "remove" : "clear";
+ executeCommand("srp client service %s %s %s", verb, serviceName, serviceType);
+ }
+
+ /**
+ * Updates an existing SRP service for the SRP client.
+ *
+ * <p>This is essentially a 'remove' and an 'add' on the SRP client's side.
+ *
+ * @param serviceName the service name like "MyService"
+ * @param serviceType the service type like "_test._tcp"
+ * @param subtypes the service subtypes like "_sub1"
+ * @param port the port number in range [1, 65535]
+ * @param txtMap the map of TXT names and values
+ * @throws TimeoutException if the service isn't updated within timeout
+ */
+ public void updateSrpService(
+ String serviceName,
+ String serviceType,
+ List<String> subtypes,
+ int port,
+ Map<String, byte[]> txtMap)
+ throws TimeoutException {
+ removeSrpService(serviceName, serviceType, false /* notifyServer */);
+ addSrpService(serviceName, serviceType, subtypes, port, txtMap);
+ }
+
+ /** Checks if an SRP service is registered. */
+ public boolean isSrpServiceRegistered(String serviceName, String serviceType) {
+ List<String> lines = executeCommand("srp client service");
+ for (String line : lines) {
+ if (line.contains(serviceName) && line.contains(serviceType)) {
+ return line.contains("Registered");
+ }
+ }
+ return false;
+ }
+
+ /** Checks if an SRP host is registered. */
+ public boolean isSrpHostRegistered() {
+ List<String> lines = executeCommand("srp client host");
+ for (String line : lines) {
+ return line.contains("Registered");
+ }
+ return false;
+ }
+
+ /** Sets the DNS server address. */
+ public void setDnsServerAddress(String address) {
+ executeCommand("dns config " + address);
+ }
+
+ /** Returns the first browsed service instance of {@code serviceType}. */
+ public NsdServiceInfo browseService(String serviceType) {
+ // CLI output:
+ // DNS browse response for _testservice._tcp.default.service.arpa.
+ // test-service
+ // Port:12345, Priority:0, Weight:0, TTL:10
+ // Host:testhost.default.service.arpa.
+ // HostAddress:2001:0:0:0:0:0:0:1 TTL:10
+ // TXT:[key1=0102, key2=03] TTL:10
+
+ List<String> lines = executeCommand("dns browse " + serviceType);
+ NsdServiceInfo info = new NsdServiceInfo();
+ info.setServiceName(lines.get(1));
+ info.setServiceType(serviceType);
+ info.setPort(DnsServiceCliOutputParser.parsePort(lines.get(2)));
+ info.setHostname(DnsServiceCliOutputParser.parseHostname(lines.get(3)));
+ info.setHostAddresses(List.of(DnsServiceCliOutputParser.parseHostAddress(lines.get(4))));
+ DnsServiceCliOutputParser.parseTxtIntoServiceInfo(lines.get(5), info);
+
+ return info;
+ }
+
+ /** Returns the resolved service instance. */
+ public NsdServiceInfo resolveService(String serviceName, String serviceType) {
+ // CLI output:
+ // DNS service resolution response for test-service for service
+ // _test._tcp.default.service.arpa.
+ // Port:12345, Priority:0, Weight:0, TTL:10
+ // Host:Android.default.service.arpa.
+ // HostAddress:2001:0:0:0:0:0:0:1 TTL:10
+ // TXT:[key1=0102, key2=03] TTL:10
+
+ List<String> lines = executeCommand("dns service %s %s", serviceName, serviceType);
+ NsdServiceInfo info = new NsdServiceInfo();
+ info.setServiceName(serviceName);
+ info.setServiceType(serviceType);
+ info.setPort(DnsServiceCliOutputParser.parsePort(lines.get(1)));
+ info.setHostname(DnsServiceCliOutputParser.parseHostname(lines.get(2)));
+ info.setHostAddresses(List.of(DnsServiceCliOutputParser.parseHostAddress(lines.get(3))));
+ DnsServiceCliOutputParser.parseTxtIntoServiceInfo(lines.get(4), info);
+
+ return info;
+ }
+
/** Runs the "factoryreset" command on the device. */
public void factoryReset() {
try {
@@ -223,7 +416,50 @@
executeCommand("ipmaddr add " + address.getHostAddress());
}
- public void ping(Inet6Address address, Inet6Address source, int size, int count) {
+ public void ping(Inet6Address address, Inet6Address source) {
+ ping(
+ address,
+ source,
+ PING_SIZE,
+ 1 /* count */,
+ PING_INTERVAL,
+ HOP_LIMIT,
+ PING_TIMEOUT_0_1_SECOND);
+ }
+
+ public void ping(Inet6Address address) {
+ ping(
+ address,
+ null,
+ PING_SIZE,
+ 1 /* count */,
+ PING_INTERVAL,
+ HOP_LIMIT,
+ PING_TIMEOUT_0_1_SECOND);
+ }
+
+ /** 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,
+ int count,
+ int interval,
+ int hopLimit,
+ float timeout) {
String cmd =
"ping"
+ ((source == null) ? "" : (" -I " + source.getHostAddress()))
@@ -232,12 +468,33 @@
+ " "
+ size
+ " "
- + count;
- executeCommand(cmd);
+ + count
+ + " "
+ + interval
+ + " "
+ + hopLimit
+ + " "
+ + timeout;
+ return executeCommand(cmd);
}
- public void ping(Inet6Address address) {
- ping(address, null, 100 /* size */, 1 /* count */);
+ 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
+ private List<String> executeCommand(String commandFormat, Object... args) {
+ return executeCommand(String.format(commandFormat, args));
}
private List<String> executeCommand(String command) {
@@ -256,15 +513,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);
+ if (line.startsWith("Error")) {
+ throw new IOException("ot-cli-ftd reported an error: " + line);
}
if (!line.startsWith("> ")) {
result.add(line);
@@ -272,4 +546,68 @@
}
return result;
}
+
+ private static String txtMapToHexString(Map<String, byte[]> txtMap) {
+ if (txtMap == null) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, byte[]> entry : txtMap.entrySet()) {
+ int length = entry.getKey().length() + entry.getValue().length + 1;
+ sb.append(String.format("%02x", length));
+ sb.append(toHexString(entry.getKey()));
+ sb.append(toHexString("="));
+ sb.append(toHexString(entry.getValue()));
+ }
+ return sb.toString();
+ }
+
+ private static String toHexString(String s) {
+ return toHexString(s.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private static String toHexString(byte[] bytes) {
+ return base16().encode(bytes);
+ }
+
+ private static final class DnsServiceCliOutputParser {
+ /** Returns the first match in the input of a given regex pattern. */
+ private static Matcher firstMatchOf(String input, String regex) {
+ Matcher matcher = Pattern.compile(regex).matcher(input);
+ matcher.find();
+ return matcher;
+ }
+
+ // Example: "Port:12345"
+ private static int parsePort(String line) {
+ return Integer.parseInt(firstMatchOf(line, "Port:(\\d+)").group(1));
+ }
+
+ // Example: "Host:Android.default.service.arpa."
+ private static String parseHostname(String line) {
+ return firstMatchOf(line, "Host:(.+)").group(1);
+ }
+
+ // Example: "HostAddress:2001:0:0:0:0:0:0:1"
+ private static InetAddress parseHostAddress(String line) {
+ return InetAddresses.parseNumericAddress(
+ firstMatchOf(line, "HostAddress:([^ ]+)").group(1));
+ }
+
+ // Example: "TXT:[key1=0102, key2=03]"
+ private static void parseTxtIntoServiceInfo(String line, NsdServiceInfo serviceInfo) {
+ String txtString = firstMatchOf(line, "TXT:\\[([^\\]]+)\\]").group(1);
+ for (String txtEntry : txtString.split(",")) {
+ String[] nameAndValue = txtEntry.trim().split("=");
+ String name = nameAndValue[0];
+ String value = nameAndValue[1];
+ byte[] bytes = new byte[value.length() / 2];
+ for (int i = 0; i < value.length(); i += 2) {
+ byte b = (byte) ((value.charAt(i) - '0') << 4 | (value.charAt(i + 1) - '0'));
+ bytes[i / 2] = b;
+ }
+ serviceInfo.setAttribute(name, bytes);
+ }
+ }
+ }
}
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 74251a6..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;
@@ -23,11 +24,25 @@
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 android.os.SystemProperties;
+
+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;
@@ -51,6 +66,7 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -66,15 +82,10 @@
public static final Duration JOIN_TIMEOUT = Duration.ofSeconds(30);
public static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
public static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
+ public static final Duration SERVICE_DISCOVERY_TIMEOUT = Duration.ofSeconds(20);
private IntegrationTestUtils() {}
- /** Returns whether the device supports simulated Thread radio. */
- public static boolean isSimulatedThreadRadioSupported() {
- // The integration test uses SIMULATION Thread radio so that it only supports CuttleFish.
- return SystemProperties.get("ro.product.model").startsWith("Cuttlefish");
- }
-
/**
* Waits for the given {@link Supplier} to be true until given timeout.
*
@@ -84,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) {
@@ -289,4 +300,175 @@
}
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 {
+ CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
+ NsdManager.DiscoveryListener listener =
+ new DefaultDiscoveryListener() {
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ serviceInfoFuture.complete(serviceInfo);
+ }
+ };
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ try {
+ serviceInfoFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+ } finally {
+ nsdManager.stopServiceDiscovery(listener);
+ }
+
+ return serviceInfoFuture.get();
+ }
+
+ /**
+ * Returns the {@link NsdServiceInfo} when a service instance of {@code serviceType} gets lost.
+ */
+ public static NsdManager.DiscoveryListener discoverForServiceLost(
+ NsdManager nsdManager,
+ String serviceType,
+ CompletableFuture<NsdServiceInfo> serviceInfoFuture) {
+ NsdManager.DiscoveryListener listener =
+ new DefaultDiscoveryListener() {
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ serviceInfoFuture.complete(serviceInfo);
+ }
+ };
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ return listener;
+ }
+
+ /** Resolves the service. */
+ public static NsdServiceInfo resolveService(NsdManager nsdManager, NsdServiceInfo serviceInfo)
+ throws Exception {
+ return resolveServiceUntil(nsdManager, serviceInfo, s -> true);
+ }
+
+ /** Returns the first resolved service that satisfies the {@code predicate}. */
+ public static NsdServiceInfo resolveServiceUntil(
+ NsdManager nsdManager, NsdServiceInfo serviceInfo, Predicate<NsdServiceInfo> predicate)
+ throws Exception {
+ CompletableFuture<NsdServiceInfo> resolvedServiceInfoFuture = new CompletableFuture<>();
+ NsdManager.ServiceInfoCallback callback =
+ new DefaultServiceInfoCallback() {
+ @Override
+ public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
+ if (predicate.test(serviceInfo)) {
+ resolvedServiceInfoFuture.complete(serviceInfo);
+ }
+ }
+ };
+ nsdManager.registerServiceInfoCallback(serviceInfo, directExecutor(), callback);
+ try {
+ return resolvedServiceInfoFuture.get(
+ SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+ } finally {
+ nsdManager.unregisterServiceInfoCallback(callback);
+ }
+ }
+
+ 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) {}
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {}
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {}
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {}
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {}
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {}
+ }
+
+ private static class DefaultServiceInfoCallback implements NsdManager.ServiceInfoCallback {
+ @Override
+ public void onServiceInfoCallbackRegistrationFailed(int errorCode) {}
+
+ @Override
+ public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {}
+
+ @Override
+ public void onServiceLost() {}
+
+ @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 4a06fe8..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,16 +56,68 @@
/** 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();
}
+ /** Returns {@code true} if the Thread interface is up. */
+ public boolean isInterfaceUp() {
+ String output = executeCommand("ifconfig");
+ return output.contains("up");
+ }
+
+ /** Returns the ML-EID of the device. */
+ public Inet6Address getMlEid() {
+ String addressStr = executeCommandAndParse("ipaddr mleid").get(0);
+ return (Inet6Address) InetAddresses.parseNumericAddress(addressStr);
+ }
+
+ /** Returns the country code on ot-daemon. */
+ public String getCountryCode() {
+ 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/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java b/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
new file mode 100644
index 0000000..7e84233
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
@@ -0,0 +1,208 @@
+/*
+ * 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 android.net.thread.utils;
+
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
+import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.ThreadNetworkController;
+import android.net.thread.ThreadNetworkController.StateCallback;
+import android.net.thread.ThreadNetworkException;
+import android.net.thread.ThreadNetworkManager;
+import android.os.OutcomeReceiver;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/** A helper class which provides synchronous API wrappers for {@link ThreadNetworkController}. */
+public final class ThreadNetworkControllerWrapper {
+ public static final Duration JOIN_TIMEOUT = Duration.ofSeconds(10);
+ public static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
+ private static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
+ private static final Duration SET_ENABLED_TIMEOUT = Duration.ofSeconds(2);
+
+ private final ThreadNetworkController mController;
+
+ /**
+ * Returns a new {@link ThreadNetworkControllerWrapper} instance or {@code null} if Thread
+ * feature is not supported on this device.
+ */
+ @Nullable
+ public static ThreadNetworkControllerWrapper newInstance(Context context) {
+ final ThreadNetworkManager manager = context.getSystemService(ThreadNetworkManager.class);
+ if (manager == null) {
+ return null;
+ }
+ return new ThreadNetworkControllerWrapper(manager.getAllThreadNetworkControllers().get(0));
+ }
+
+ private ThreadNetworkControllerWrapper(ThreadNetworkController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Returns the Thread enabled state.
+ *
+ * <p>The value can be one of {@code ThreadNetworkController#STATE_*}.
+ */
+ public final int getEnabledState()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ CompletableFuture<Integer> future = new CompletableFuture<>();
+ StateCallback callback =
+ new StateCallback() {
+ @Override
+ public void onThreadEnableStateChanged(int enabledState) {
+ future.complete(enabledState);
+ }
+
+ @Override
+ public void onDeviceRoleChanged(int deviceRole) {}
+ };
+
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> mController.registerStateCallback(directExecutor(), callback));
+ try {
+ return future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
+ }
+ }
+
+ /**
+ * Returns the Thread device role.
+ *
+ * <p>The value can be one of {@code ThreadNetworkController#DEVICE_ROLE_*}.
+ */
+ public final int getDeviceRole()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ CompletableFuture<Integer> future = new CompletableFuture<>();
+ StateCallback callback = future::complete;
+
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> mController.registerStateCallback(directExecutor(), callback));
+ try {
+ return future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
+ }
+ }
+
+ /** An synchronous variant of {@link ThreadNetworkController#setEnabled}. */
+ public void setEnabledAndWait(boolean enabled)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ () ->
+ mController.setEnabled(
+ enabled, directExecutor(), newOutcomeReceiver(future)));
+ future.get(SET_ENABLED_TIMEOUT.toSeconds(), SECONDS);
+ }
+
+ /** Joins the given network and wait for this device to become attached. */
+ public void joinAndWait(ActiveOperationalDataset activeDataset)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ () ->
+ mController.join(
+ activeDataset, directExecutor(), newOutcomeReceiver(future)));
+ future.get(JOIN_TIMEOUT.toSeconds(), SECONDS);
+ }
+
+ /** An synchronous variant of {@link ThreadNetworkController#leave}. */
+ public void leaveAndWait() throws InterruptedException, ExecutionException, TimeoutException {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ () -> mController.leave(directExecutor(), future::complete));
+ future.get(LEAVE_TIMEOUT.toSeconds(), SECONDS);
+ }
+
+ /** Waits for the device role to become {@code deviceRole}. */
+ public int waitForRole(int deviceRole, Duration timeout)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return waitForRoleAnyOf(List.of(deviceRole), timeout);
+ }
+
+ /** Waits for the device role to become one of the values specified in {@code deviceRoles}. */
+ public int waitForRoleAnyOf(List<Integer> deviceRoles, Duration timeout)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ CompletableFuture<Integer> future = new CompletableFuture<>();
+ ThreadNetworkController.StateCallback callback =
+ newRole -> {
+ if (deviceRoles.contains(newRole)) {
+ future.complete(newRole);
+ }
+ };
+
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> mController.registerStateCallback(directExecutor(), callback));
+
+ try {
+ return future.get(timeout.toSeconds(), SECONDS);
+ } finally {
+ mController.unregisterStateCallback(callback);
+ }
+ }
+
+ /** An synchronous variant of {@link ThreadNetworkController#setTestNetworkAsUpstream}. */
+ public void setTestNetworkAsUpstreamAndWait(@Nullable String networkInterfaceName)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ NETWORK_SETTINGS,
+ () -> {
+ mController.setTestNetworkAsUpstream(
+ networkInterfaceName, directExecutor(), future::complete);
+ });
+ future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
+ }
+
+ private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
+ CompletableFuture<V> future) {
+ return new OutcomeReceiver<V, ThreadNetworkException>() {
+ @Override
+ public void onResult(V result) {
+ future.complete(result);
+ }
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ future.completeExceptionally(e);
+ }
+ };
+ }
+}
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/android/net/thread/OperationalDatasetTimestampTest.java b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
index 2244a89..11c78e3 100644
--- a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
+++ b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
@@ -41,6 +41,19 @@
}
@Test
+ public void fromInstant_authoritativeIsSetAsSpecified() {
+ Instant instant = Instant.now();
+
+ OperationalDatasetTimestamp timestampAuthoritativeFalse =
+ OperationalDatasetTimestamp.fromInstant(instant, false);
+ OperationalDatasetTimestamp timestampAuthoritativeTrue =
+ OperationalDatasetTimestamp.fromInstant(instant, true);
+
+ assertThat(timestampAuthoritativeFalse.isAuthoritativeSource()).isEqualTo(false);
+ assertThat(timestampAuthoritativeTrue.isAuthoritativeSource()).isEqualTo(true);
+ }
+
+ @Test
public void fromTlvValue_goodValue_success() {
OperationalDatasetTimestamp timestamp =
OperationalDatasetTimestamp.fromTlvValue(base16().decode("FFEEDDCCBBAA9989"));
diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
index 75eb043..ac74372 100644
--- a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
+++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
@@ -19,6 +19,7 @@
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_CHANNEL;
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_OPERATION;
import static android.os.Process.SYSTEM_UID;
import static com.google.common.io.BaseEncoding.base16;
@@ -33,6 +34,7 @@
import android.os.Binder;
import android.os.OutcomeReceiver;
import android.os.Process;
+import android.util.SparseIntArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -77,6 +79,13 @@
private static final ActiveOperationalDataset DEFAULT_DATASET =
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+ private static final SparseIntArray DEFAULT_CHANNEL_POWERS =
+ new SparseIntArray() {
+ {
+ put(20, 32767);
+ }
+ };
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -111,6 +120,10 @@
return (IOperationReceiver) invocation.getArguments()[1];
}
+ private static IOperationReceiver getSetChannelMaxPowersReceiver(InvocationOnMock invocation) {
+ return (IOperationReceiver) invocation.getArguments()[1];
+ }
+
private static IActiveOperationalDatasetReceiver getCreateDatasetReceiver(
InvocationOnMock invocation) {
return (IActiveOperationalDatasetReceiver) invocation.getArguments()[1];
@@ -361,6 +374,51 @@
}
@Test
+ public void setChannelMaxPowers_callbackIsInvokedWithCallingAppIdentity() throws Exception {
+ setBinderUid(SYSTEM_UID);
+
+ AtomicInteger successCallbackUid = new AtomicInteger(0);
+ AtomicInteger errorCallbackUid = new AtomicInteger(0);
+
+ doAnswer(
+ invoke -> {
+ getSetChannelMaxPowersReceiver(invoke).onSuccess();
+ return null;
+ })
+ .when(mMockService)
+ .setChannelMaxPowers(any(ChannelMaxPower[].class), any(IOperationReceiver.class));
+ mController.setChannelMaxPowers(
+ DEFAULT_CHANNEL_POWERS,
+ Runnable::run,
+ v -> successCallbackUid.set(Binder.getCallingUid()));
+ doAnswer(
+ invoke -> {
+ getSetChannelMaxPowersReceiver(invoke)
+ .onError(ERROR_UNSUPPORTED_OPERATION, "");
+ return null;
+ })
+ .when(mMockService)
+ .setChannelMaxPowers(any(ChannelMaxPower[].class), any(IOperationReceiver.class));
+ mController.setChannelMaxPowers(
+ DEFAULT_CHANNEL_POWERS,
+ Runnable::run,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Void unused) {}
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ errorCallbackUid.set(Binder.getCallingUid());
+ }
+ });
+
+ assertThat(successCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(successCallbackUid.get()).isEqualTo(Process.myUid());
+ assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid());
+ }
+
+ @Test
public void setTestNetworkAsUpstream_callbackIsInvokedWithCallingAppIdentity()
throws Exception {
setBinderUid(SYSTEM_UID);
diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java
index f62b437..5908c20 100644
--- a/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java
+++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java
@@ -32,6 +32,6 @@
public void constructor_tooLargeErrorCode_throwsIllegalArgumentException() throws Exception {
// TODO (b/323791003): move this test case to cts/ThreadNetworkExceptionTest when mainline
// CTS is ready.
- assertThrows(IllegalArgumentException.class, () -> new ThreadNetworkException(13, "13"));
+ assertThrows(IllegalArgumentException.class, () -> new ThreadNetworkException(14, "14"));
}
}
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 8aea0a3..b32986d 100644
--- a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -16,43 +16,73 @@
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;
import static com.google.common.truth.Truth.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+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 androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
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;
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;
+import java.net.InetAddress;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
/** Unit tests for {@link NsdPublisher}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class NsdPublisherTest {
+ private static final DnsTxtAttribute TEST_TXT_ENTRY_1 =
+ new DnsTxtAttribute("key1", new byte[] {0x01, 0x02});
+ private static final DnsTxtAttribute TEST_TXT_ENTRY_2 =
+ new DnsTxtAttribute("key2", new byte[] {0x03});
+
@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;
@@ -66,19 +96,15 @@
public void registerService_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
-
mTestLooper.dispatchAll();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@@ -105,11 +131,10 @@
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
- assertThat(actualServiceInfo.getAttributes().get("key1"))
- .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
- assertThat(actualServiceInfo.getAttributes().get("key2"))
- .isEqualTo(new byte[] {(byte) 0x03});
-
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
+ .isEqualTo(TEST_TXT_ENTRY_1.value);
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
+ .isEqualTo(TEST_TXT_ENTRY_2.value);
verify(mRegistrationReceiver, times(1)).onSuccess();
}
@@ -117,19 +142,15 @@
public void registerService_nsdManagerFails_serviceRegistrationFails() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
-
mTestLooper.dispatchAll();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@@ -156,21 +177,16 @@
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
- assertThat(actualServiceInfo.getAttributes().get("key1"))
- .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
- assertThat(actualServiceInfo.getAttributes().get("key2"))
- .isEqualTo(new byte[] {(byte) 0x03});
-
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
+ .isEqualTo(TEST_TXT_ENTRY_1.value);
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
+ .isEqualTo(TEST_TXT_ENTRY_2.value);
verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
}
@Test
public void registerService_nsdManagerThrows_serviceRegistrationFails() throws Exception {
prepareTest();
-
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
doThrow(new IllegalArgumentException("NsdManager fails"))
.when(mMockNsdManager)
.registerService(any(), anyInt(), any(Executor.class), any());
@@ -181,7 +197,7 @@
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
@@ -194,16 +210,13 @@
throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
@@ -239,16 +252,13 @@
public void unregisterService_nsdManagerFails_serviceUnregistrationFails() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
@@ -282,12 +292,425 @@
}
@Test
- public void onOtDaemonDied_unregisterAll() {
+ public void registerHost_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mTestLooper.dispatchAll();
+
+ assertThat(actualServiceInfo.getServiceName()).isNull();
+ assertThat(actualServiceInfo.getServiceType()).isNull();
+ assertThat(actualServiceInfo.getSubtypes()).isEmpty();
+ assertThat(actualServiceInfo.getPort()).isEqualTo(0);
+ assertThat(actualServiceInfo.getAttributes()).isEmpty();
+ assertThat(actualServiceInfo.getHostname()).isEqualTo("MyHost");
+ assertThat(actualServiceInfo.getHostAddresses())
+ .isEqualTo(makeAddresses("2001:db8::1", "2001:db8::2", "2001:db8::3"));
+
+ verify(mRegistrationReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void registerHost_nsdManagerFails_serviceRegistrationFails() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(),
+ actualRegistrationListenerCaptor.capture());
+ mTestLooper.dispatchAll();
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onRegistrationFailed(actualServiceInfo, FAILURE_INTERNAL_ERROR);
+ mTestLooper.dispatchAll();
+
+ assertThat(actualServiceInfo.getServiceName()).isNull();
+ assertThat(actualServiceInfo.getServiceType()).isNull();
+ assertThat(actualServiceInfo.getSubtypes()).isEmpty();
+ assertThat(actualServiceInfo.getPort()).isEqualTo(0);
+ assertThat(actualServiceInfo.getAttributes()).isEmpty();
+ assertThat(actualServiceInfo.getHostname()).isEqualTo("MyHost");
+ assertThat(actualServiceInfo.getHostAddresses())
+ .isEqualTo(makeAddresses("2001:db8::1", "2001:db8::2", "2001:db8::3"));
+
+ verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void registerHost_nsdManagerThrows_serviceRegistrationFails() throws Exception {
+ prepareTest();
+
+ doThrow(new IllegalArgumentException("NsdManager fails"))
+ .when(mMockNsdManager)
+ .registerService(any(), anyInt(), any(Executor.class), any());
+
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void unregisterHost_nsdManagerSucceeds_serviceUnregistrationSucceeds() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+ verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+ actualRegistrationListener.onServiceUnregistered(actualServiceInfo);
+ mTestLooper.dispatchAll();
+ verify(mUnregistrationReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void unregisterHost_nsdManagerFails_serviceUnregistrationFails() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+ verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+ actualRegistrationListener.onUnregistrationFailed(
+ actualServiceInfo, FAILURE_INTERNAL_ERROR);
+ mTestLooper.dispatchAll();
+ verify(mUnregistrationReceiver, times(1)).onError(0);
+ }
+
+ @Test
+ public void discoverService_serviceDiscovered() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.discoverService("_test._tcp", mDiscoverServiceCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<NsdManager.DiscoveryListener> discoveryListenerArgumentCaptor =
+ ArgumentCaptor.forClass(NsdManager.DiscoveryListener.class);
+ verify(mMockNsdManager, times(1))
+ .discoverServices(
+ eq(new DiscoveryRequest.Builder(PROTOCOL_DNS_SD, "_test._tcp").build()),
+ any(Executor.class),
+ discoveryListenerArgumentCaptor.capture());
+ NsdManager.DiscoveryListener actualDiscoveryListener =
+ discoveryListenerArgumentCaptor.getValue();
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+ serviceInfo.setServiceName("test");
+ serviceInfo.setServiceType(null);
+ actualDiscoveryListener.onServiceFound(serviceInfo);
+ mTestLooper.dispatchAll();
+
+ verify(mDiscoverServiceCallback, times(1))
+ .onServiceDiscovered("test", "_test._tcp", true /* isFound */);
+ }
+
+ @Test
+ public void discoverService_serviceLost() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.discoverService("_test._tcp", mDiscoverServiceCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<NsdManager.DiscoveryListener> discoveryListenerArgumentCaptor =
+ ArgumentCaptor.forClass(NsdManager.DiscoveryListener.class);
+ verify(mMockNsdManager, times(1))
+ .discoverServices(
+ eq(new DiscoveryRequest.Builder(PROTOCOL_DNS_SD, "_test._tcp").build()),
+ any(Executor.class),
+ discoveryListenerArgumentCaptor.capture());
+ NsdManager.DiscoveryListener actualDiscoveryListener =
+ discoveryListenerArgumentCaptor.getValue();
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+ serviceInfo.setServiceName("test");
+ serviceInfo.setServiceType(null);
+ actualDiscoveryListener.onServiceLost(serviceInfo);
+ mTestLooper.dispatchAll();
+
+ verify(mDiscoverServiceCallback, times(1))
+ .onServiceDiscovered("test", "_test._tcp", false /* isFound */);
+ }
+
+ @Test
+ public void stopServiceDiscovery() {
+ prepareTest();
+
+ mNsdPublisher.discoverService("_test._tcp", mDiscoverServiceCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<NsdManager.DiscoveryListener> discoveryListenerArgumentCaptor =
+ ArgumentCaptor.forClass(NsdManager.DiscoveryListener.class);
+ verify(mMockNsdManager, times(1))
+ .discoverServices(
+ eq(new DiscoveryRequest.Builder(PROTOCOL_DNS_SD, "_test._tcp").build()),
+ any(Executor.class),
+ discoveryListenerArgumentCaptor.capture());
+ NsdManager.DiscoveryListener actualDiscoveryListener =
+ discoveryListenerArgumentCaptor.getValue();
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+ serviceInfo.setServiceName("test");
+ serviceInfo.setServiceType(null);
+ actualDiscoveryListener.onServiceFound(serviceInfo);
+ mNsdPublisher.stopServiceDiscovery(10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(1)).stopServiceDiscovery(actualDiscoveryListener);
+ }
+
+ @Test
+ public void resolveService_serviceResolved() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveService(
+ "test", "_test._tcp", mResolveServiceCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<NsdServiceInfo> serviceInfoArgumentCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.ServiceInfoCallback> serviceInfoCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(NsdManager.ServiceInfoCallback.class);
+ verify(mMockNsdManager, times(1))
+ .registerServiceInfoCallback(
+ serviceInfoArgumentCaptor.capture(),
+ any(Executor.class),
+ serviceInfoCallbackArgumentCaptor.capture());
+ assertThat(serviceInfoArgumentCaptor.getValue().getServiceName()).isEqualTo("test");
+ assertThat(serviceInfoArgumentCaptor.getValue().getServiceType()).isEqualTo("_test._tcp");
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+ serviceInfo.setServiceName("test");
+ serviceInfo.setServiceType("_test._tcp");
+ serviceInfo.setPort(12345);
+ serviceInfo.setHostname("test-host");
+ serviceInfo.setHostAddresses(
+ List.of(
+ InetAddress.parseNumericAddress("2001::1"),
+ InetAddress.parseNumericAddress("2001::2")));
+ serviceInfo.setAttribute(TEST_TXT_ENTRY_1.name, TEST_TXT_ENTRY_1.value);
+ serviceInfo.setAttribute(TEST_TXT_ENTRY_2.name, TEST_TXT_ENTRY_2.value);
+ serviceInfoCallbackArgumentCaptor.getValue().onServiceUpdated(serviceInfo);
+ mTestLooper.dispatchAll();
+
+ verify(mResolveServiceCallback, times(1))
+ .onServiceResolved(
+ eq("test-host"),
+ eq("test"),
+ eq("_test._tcp"),
+ eq(12345),
+ eq(List.of("2001::1", "2001::2")),
+ (List<DnsTxtAttribute>)
+ argThat(containsInAnyOrder(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2)),
+ anyInt());
+ }
+
+ @Test
+ public void stopServiceResolution() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveService(
+ "test", "_test._tcp", mResolveServiceCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<NsdServiceInfo> serviceInfoArgumentCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.ServiceInfoCallback> serviceInfoCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(NsdManager.ServiceInfoCallback.class);
+ verify(mMockNsdManager, times(1))
+ .registerServiceInfoCallback(
+ serviceInfoArgumentCaptor.capture(),
+ any(Executor.class),
+ serviceInfoCallbackArgumentCaptor.capture());
+ assertThat(serviceInfoArgumentCaptor.getValue().getServiceName()).isEqualTo("test");
+ assertThat(serviceInfoArgumentCaptor.getValue().getServiceType()).isEqualTo("_test._tcp");
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+ serviceInfo.setServiceName("test");
+ serviceInfo.setServiceType("_test._tcp");
+ serviceInfo.setPort(12345);
+ serviceInfo.setHostname("test-host");
+ serviceInfo.setHostAddresses(
+ List.of(
+ InetAddress.parseNumericAddress("2001::1"),
+ InetAddress.parseNumericAddress("2001::2")));
+ serviceInfo.setAttribute("key1", new byte[] {(byte) 0x01, (byte) 0x02});
+ serviceInfo.setAttribute("key2", new byte[] {(byte) 0x03});
+ serviceInfoCallbackArgumentCaptor.getValue().onServiceUpdated(serviceInfo);
+ mNsdPublisher.stopServiceResolution(10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(1))
+ .unregisterServiceInfoCallback(serviceInfoCallbackArgumentCaptor.getValue());
+ }
+
+ @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();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
ArgumentCaptor.forClass(NsdServiceInfo.class);
ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
@@ -299,7 +722,7 @@
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
@@ -336,24 +759,50 @@
actualRegistrationListenerCaptor.getAllValues().get(1);
actualListener2.onServiceRegistered(actualServiceInfoCaptor.getValue());
- mNsdPublisher.onOtDaemonDied();
+ mNsdPublisher.registerHost(
+ "Myhost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 18 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(3))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+ NsdManager.RegistrationListener actualListener3 =
+ actualRegistrationListenerCaptor.getAllValues().get(1);
+ actualListener3.onServiceRegistered(actualServiceInfoCaptor.getValue());
+
+ mNsdPublisher.reset();
mTestLooper.dispatchAll();
verify(mMockNsdManager, times(1)).unregisterService(actualListener1);
verify(mMockNsdManager, times(1)).unregisterService(actualListener2);
+ verify(mMockNsdManager, times(1)).unregisterService(actualListener3);
}
- private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
- DnsTxtAttribute txtAttribute = new DnsTxtAttribute();
+ @Test
+ public void onOtDaemonDied_resetIsCalled() {
+ prepareTest();
+ NsdPublisher spyNsdPublisher = spy(mNsdPublisher);
- txtAttribute.name = name;
- txtAttribute.value = new byte[value.size()];
+ spyNsdPublisher.onOtDaemonDied();
+ mTestLooper.dispatchAll();
- for (int i = 0; i < value.size(); ++i) {
- txtAttribute.value[i] = value.get(i).byteValue();
+ verify(spyNsdPublisher, times(1)).reset();
+ }
+
+ private static List<InetAddress> makeAddresses(String... addressStrings) {
+ List<InetAddress> addresses = new ArrayList<>();
+
+ for (String addressString : addressStrings) {
+ addresses.add(InetAddresses.parseNumericAddress(addressString));
}
-
- return txtAttribute;
+ return addresses;
}
// @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
@@ -362,6 +811,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 60a5f2b..6e2369f 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -21,9 +21,12 @@
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
+import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
import static android.net.thread.ThreadNetworkManager.DISALLOW_THREAD_NETWORK;
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;
@@ -33,10 +36,12 @@
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;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -47,34 +52,55 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkAgent;
import android.net.NetworkProvider;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IActiveOperationalDatasetReceiver;
import android.net.thread.IOperationReceiver;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkException;
import android.os.Handler;
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.DnsTxtAttribute;
+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;
+import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -82,6 +108,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
@@ -106,27 +138,43 @@
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");
+ private static final String TEST_VENDOR_OUI = "AC-DE-48";
+ private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
+ private static final String TEST_VENDOR_NAME = "test vendor";
+ private static final String TEST_MODEL_NAME = "test model";
+ private static final boolean TEST_VGH_VALUE = false;
+
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@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;
+ @Mock Resources mResources;
+ @Mock ConnectivityResources mConnectivityResources;
+
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());
@@ -143,9 +191,25 @@
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)))
+ .thenReturn(TEST_VENDOR_OUI);
+ when(mResources.getString(eq(R.string.config_thread_model_name)))
+ .thenReturn(TEST_MODEL_NAME);
+ when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+ .thenReturn(TEST_VGH_VALUE);
+
+ final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
+ mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
+ mPersistentSettings.initialize();
+
mService =
new ThreadNetworkControllerService(
mContext,
@@ -155,9 +219,11 @@
mMockConnectivityManager,
mMockTunIfController,
mMockInfraIfController,
- mMockPersistentSettings,
+ mPersistentSettings,
mMockNsdPublisher,
- mMockUserManager);
+ mMockUserManager,
+ mConnectivityResources,
+ () -> DEFAULT_COUNTRY_CODE);
mService.setTestNetworkAgent(mMockNetworkAgent);
}
@@ -174,6 +240,109 @@
}
@Test
+ public void initialize_resourceOverlayValuesAreSetToOtDaemon() throws Exception {
+ when(mResources.getString(eq(R.string.config_thread_vendor_name)))
+ .thenReturn(TEST_VENDOR_NAME);
+ when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
+ .thenReturn(TEST_VENDOR_OUI);
+ when(mResources.getString(eq(R.string.config_thread_model_name)))
+ .thenReturn(TEST_MODEL_NAME);
+ when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+ .thenReturn(true);
+
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ MeshcopTxtAttributes meshcopTxts = mFakeOtDaemon.getOverriddenMeshcopTxtAttributes();
+ assertThat(meshcopTxts.vendorName).isEqualTo(TEST_VENDOR_NAME);
+ assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);
+ assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
+ assertThat(meshcopTxts.nonStandardTxtEntries)
+ .containsExactly(new DnsTxtAttribute("vgh", "1".getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_managedByGoogleIsFalse_vghIsZero() {
+ when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+ .thenReturn(false);
+
+ MeshcopTxtAttributes meshcopTxts =
+ ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
+
+ assertThat(meshcopTxts.nonStandardTxtEntries)
+ .containsExactly(new DnsTxtAttribute("vgh", "0".getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_emptyVendorName_accepted() {
+ when(mResources.getString(eq(R.string.config_thread_vendor_name))).thenReturn("");
+
+ MeshcopTxtAttributes meshcopTxts =
+ ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
+
+ assertThat(meshcopTxts.vendorName).isEqualTo("");
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_tooLongVendorName_throwsIllegalStateException() {
+ when(mResources.getString(eq(R.string.config_thread_vendor_name)))
+ .thenReturn("vendor name is 25 bytes!!");
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_tooLongModelName_throwsIllegalStateException() {
+ when(mResources.getString(eq(R.string.config_thread_model_name)))
+ .thenReturn("model name is 25 bytes!!!");
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_emptyModelName_accepted() {
+ when(mResources.getString(eq(R.string.config_thread_model_name))).thenReturn("");
+
+ var meshcopTxts = ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
+ assertThat(meshcopTxts.modelName).isEqualTo("");
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_invalidVendorOui_throwsIllegalStateException() {
+ assertThrows(
+ IllegalStateException.class, () -> getMeshcopTxtAttributesWithVendorOui("ABCDEFA"));
+ assertThrows(
+ IllegalStateException.class, () -> getMeshcopTxtAttributesWithVendorOui("ABCDEG"));
+ assertThrows(
+ IllegalStateException.class, () -> getMeshcopTxtAttributesWithVendorOui("ABCD"));
+ assertThrows(
+ IllegalStateException.class,
+ () -> getMeshcopTxtAttributesWithVendorOui("AB.CD.EF"));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_validVendorOui_accepted() {
+ assertThat(getMeshcopTxtAttributesWithVendorOui("010203")).isEqualTo(new byte[] {1, 2, 3});
+ assertThat(getMeshcopTxtAttributesWithVendorOui("01-02-03"))
+ .isEqualTo(new byte[] {1, 2, 3});
+ assertThat(getMeshcopTxtAttributesWithVendorOui("01:02:03"))
+ .isEqualTo(new byte[] {1, 2, 3});
+ assertThat(getMeshcopTxtAttributesWithVendorOui("ABCDEF"))
+ .isEqualTo(new byte[] {(byte) 0xAB, (byte) 0xCD, (byte) 0xEF});
+ assertThat(getMeshcopTxtAttributesWithVendorOui("abcdef"))
+ .isEqualTo(new byte[] {(byte) 0xAB, (byte) 0xCD, (byte) 0xEF});
+ }
+
+ private byte[] getMeshcopTxtAttributesWithVendorOui(String vendorOui) {
+ when(mResources.getString(eq(R.string.config_thread_vendor_oui))).thenReturn(vendorOui);
+ return ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources).vendorOui;
+ }
+
+ @Test
public void join_otDaemonRemoteFailure_returnsInternalError() throws Exception {
mService.initialize();
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
@@ -204,13 +373,13 @@
}
@Test
- public void userRestriction_initWithUserRestricted_threadIsDisabled() {
+ public void userRestriction_initWithUserRestricted_otDaemonNotStarted() {
when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
mService.initialize();
mTestLooper.dispatchAll();
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+ assertThat(mFakeOtDaemon.isInitialized()).isFalse();
}
@Test
@@ -225,15 +394,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();
@@ -242,21 +405,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();
@@ -265,8 +421,6 @@
mTestLooper.dispatchAll();
assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
- verify(mMockPersistentSettings, never())
- .put(eq(ThreadPersistentSettings.THREAD_ENABLED.key), eq(true));
}
@Test
@@ -283,6 +437,116 @@
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)));
+
+ return receiverRef;
+ }
+
private static IOperationReceiver newOperationReceiver(CompletableFuture<Void> future) {
return new IOperationReceiver.Stub() {
@Override
@@ -298,6 +562,102 @@
}
@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_zeroNanoseconds_returnsZeroTicks() throws Exception {
+ Instant now = Instant.ofEpochSecond(0, 0);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ 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().getTicks()).isEqualTo(0);
+ }
+
+ @Test
+ public void createRandomizedDataset_maxNanoseconds_returnsMaxTicks() throws Exception {
+ // The nanoseconds to ticks conversion is rounded in the current implementation.
+ // 32767.5 / 32768 * 1000000000 = 999984741.2109375, using 999984741 to
+ // produce the maximum ticks.
+ Instant now = Instant.ofEpochSecond(0, 999984741);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ 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().getTicks()).isEqualTo(32767);
+ }
+
+ @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);
@@ -332,4 +692,101 @@
verify(mockReceiver, never()).onSuccess(any(ActiveOperationalDataset.class));
verify(mockReceiver, times(1)).onError(eq(ERROR_INTERNAL_ERROR), anyString());
}
+
+ @Test
+ public void forceStopOtDaemonForTest_noPermission_throwsSecurityException() {
+ doThrow(new SecurityException(""))
+ .when(mContext)
+ .enforceCallingOrSelfPermission(eq(PERMISSION_THREAD_NETWORK_PRIVILEGED), any());
+
+ assertThrows(
+ SecurityException.class,
+ () -> mService.forceStopOtDaemonForTest(true, new IOperationReceiver.Default()));
+ }
+
+ @Test
+ public void forceStopOtDaemonForTest_enabled_otDaemonDiesAndJoinFails() throws Exception {
+ mService.initialize();
+ IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
+ IOperationReceiver mockJoinReceiver = mock(IOperationReceiver.class);
+
+ mService.forceStopOtDaemonForTest(true, mockReceiver);
+ mTestLooper.dispatchAll();
+ mService.join(DEFAULT_ACTIVE_DATASET, mockJoinReceiver);
+ mTestLooper.dispatchAll();
+
+ verify(mockReceiver, times(1)).onSuccess();
+ assertThat(mFakeOtDaemon.isInitialized()).isFalse();
+ verify(mockJoinReceiver, times(1)).onError(eq(ERROR_THREAD_DISABLED), anyString());
+ }
+
+ @Test
+ public void forceStopOtDaemonForTest_disable_otDaemonRestartsAndJoinSccess() throws Exception {
+ mService.initialize();
+ IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
+ IOperationReceiver mockJoinReceiver = mock(IOperationReceiver.class);
+
+ mService.forceStopOtDaemonForTest(true, mock(IOperationReceiver.class));
+ mTestLooper.dispatchAll();
+ mService.forceStopOtDaemonForTest(false, mockReceiver);
+ mTestLooper.dispatchAll();
+ mService.join(DEFAULT_ACTIVE_DATASET, mockJoinReceiver);
+ mTestLooper.dispatchAll();
+ mTestLooper.moveTimeForward(FakeOtDaemon.JOIN_DELAY.toMillis() + 100);
+ mTestLooper.dispatchAll();
+
+ verify(mockReceiver, times(1)).onSuccess();
+ assertThat(mFakeOtDaemon.isInitialized()).isTrue();
+ verify(mockJoinReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void onOtDaemonDied_joinedNetwork_interfaceStateBackToUp() throws Exception {
+ mService.initialize();
+ final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
+ mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver);
+ mTestLooper.dispatchAll();
+ mTestLooper.moveTimeForward(FakeOtDaemon.JOIN_DELAY.toMillis() + 100);
+ mTestLooper.dispatchAll();
+
+ Mockito.reset(mMockInfraIfController);
+ mFakeOtDaemon.terminate();
+ mTestLooper.dispatchAll();
+
+ verify(mMockTunIfController, times(1)).onOtDaemonDied();
+ InOrder inOrder = Mockito.inOrder(mMockTunIfController);
+ inOrder.verify(mMockTunIfController, times(1)).setInterfaceUp(false);
+ inOrder.verify(mMockTunIfController, times(1)).setInterfaceUp(true);
+ }
+
+ @Test
+ public void setConfiguration_configurationUpdated() throws Exception {
+ mService.initialize();
+ final IOperationReceiver mockReceiver1 = mock(IOperationReceiver.class);
+ final IOperationReceiver mockReceiver2 = mock(IOperationReceiver.class);
+ final IOperationReceiver mockReceiver3 = mock(IOperationReceiver.class);
+ ThreadConfiguration config1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(false)
+ .setDhcp6PdEnabled(false)
+ .build();
+ ThreadConfiguration config2 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcp6PdEnabled(true)
+ .build();
+ ThreadConfiguration config3 =
+ new ThreadConfiguration.Builder(config2).build(); // Same as config2
+
+ mService.setConfiguration(config1, mockReceiver1);
+ mService.setConfiguration(config2, mockReceiver2);
+ mService.setConfiguration(config3, mockReceiver3);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPersistentSettings.getConfiguration()).isEqualTo(config3);
+ InOrder inOrder = Mockito.inOrder(mockReceiver1, mockReceiver2, mockReceiver3);
+ inOrder.verify(mockReceiver1).onSuccess();
+ inOrder.verify(mockReceiver2).onSuccess();
+ inOrder.verify(mockReceiver3).onSuccess();
+ }
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
index 5ca6511..ca9741d 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
@@ -19,6 +19,7 @@
import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE;
+import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE;
import static com.google.common.truth.Truth.assertThat;
@@ -104,6 +105,7 @@
@Mock List<SubscriptionInfo> mSubscriptionInfoList;
@Mock SubscriptionInfo mSubscriptionInfo0;
@Mock SubscriptionInfo mSubscriptionInfo1;
+ @Mock ThreadPersistentSettings mPersistentSettings;
private ThreadNetworkCountryCode mThreadNetworkCountryCode;
private boolean mErrorSetCountryCode;
@@ -164,7 +166,8 @@
mContext,
mTelephonyManager,
mSubscriptionManager,
- oemCountryCode);
+ oemCountryCode,
+ mPersistentSettings);
}
private static Address newAddress(String countryCode) {
@@ -450,6 +453,14 @@
}
@Test
+ public void settingsCountryCode_settingsCountryCodeIsActive_settingsCountryCodeIsUsed() {
+ when(mPersistentSettings.get(THREAD_COUNTRY_CODE)).thenReturn(TEST_COUNTRY_CODE_CN);
+ mThreadNetworkCountryCode.initialize();
+
+ assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_CN);
+ }
+
+ @Test
public void dump_allCountryCodeInfoAreDumped() {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
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 c7e0eca..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,17 +16,29 @@
package com.android.server.thread;
+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;
@@ -34,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;
@@ -44,20 +57,44 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ThreadNetworkShellCommandTest {
- private static final String TAG = "ThreadNetworkShellCommandTTest";
- @Mock ThreadNetworkService mThreadNetworkService;
- @Mock ThreadNetworkCountryCode mThreadNetworkCountryCode;
- @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 mThreadNetworkShellCommand;
+ @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);
- mThreadNetworkShellCommand = new ThreadNetworkShellCommand(mThreadNetworkCountryCode);
- mThreadNetworkShellCommand.setPrintWriters(mOutputWriter, mErrorWriter);
+ 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);
}
@After
@@ -66,11 +103,26 @@
}
@Test
- public void getCountryCode_executeInUnrootedShell_allowed() {
- BinderUtil.setUid(Process.SHELL_UID);
- when(mThreadNetworkCountryCode.getCountryCode()).thenReturn("US");
+ public void getCountryCode_testingPermissionIsChecked() {
+ when(mCountryCode.getCountryCode()).thenReturn("US");
- mThreadNetworkShellCommand.exec(
+ 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(
new Binder(),
new FileDescriptor(),
new FileDescriptor(),
@@ -81,60 +133,172 @@
}
@Test
- public void forceSetCountryCodeEnabled_executeInUnrootedShell_notAllowed() {
- BinderUtil.setUid(Process.SHELL_UID);
-
- mThreadNetworkShellCommand.exec(
+ public void forceSetCountryCodeEnabled_testingPermissionIsChecked() {
+ mShellCommand.exec(
new Binder(),
new FileDescriptor(),
new FileDescriptor(),
new FileDescriptor(),
new String[] {"force-country-code", "enabled", "US"});
- verify(mThreadNetworkCountryCode, 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);
-
- mThreadNetworkShellCommand.exec(
+ public void forceSetCountryCodeEnabled_countryCodeIsOverridden() {
+ mShellCommand.exec(
new Binder(),
new FileDescriptor(),
new FileDescriptor(),
new FileDescriptor(),
new String[] {"force-country-code", "enabled", "US"});
- verify(mThreadNetworkCountryCode).setOverrideCountryCode(eq("US"));
+ verify(mCountryCode).setOverrideCountryCode(eq("US"));
}
@Test
- public void forceSetCountryCodeDisabled_executeInUnrootedShell_notAllowed() {
- BinderUtil.setUid(Process.SHELL_UID);
-
- mThreadNetworkShellCommand.exec(
+ public void forceSetCountryCodeDisabled_overriddenCountryCodeIsCleared() {
+ mShellCommand.exec(
new Binder(),
new FileDescriptor(),
new FileDescriptor(),
new FileDescriptor(),
new String[] {"force-country-code", "disabled"});
- verify(mThreadNetworkCountryCode, never()).setOverrideCountryCode(any());
- verify(mErrorWriter).println(contains("force-country-code"));
+ verify(mCountryCode).clearOverrideCountryCode();
}
@Test
- public void forceSetCountryCodeDisabled_executeInRootedShell_allowed() {
- BinderUtil.setUid(Process.ROOT_UID);
-
- mThreadNetworkShellCommand.exec(
+ public void forceStopOtDaemon_testingPermissionIsChecked() {
+ mShellCommand.exec(
new Binder(),
new FileDescriptor(),
new FileDescriptor(),
new FileDescriptor(),
- new String[] {"force-country-code", "disabled"});
+ new String[] {"force-stop-ot-daemon", "enabled"});
- verify(mThreadNetworkCountryCode).clearOverrideCountryCode();
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
+ }
+
+ @Test
+ public void forceStopOtDaemon_serviceThrows_failed() {
+ doThrow(new SecurityException(""))
+ .when(mControllerService)
+ .forceStopOtDaemonForTest(eq(true), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"force-stop-ot-daemon", "enabled"});
+
+ verify(mControllerService, times(1)).forceStopOtDaemonForTest(eq(true), any());
+ verify(mOutputWriter, never()).println();
+ }
+
+ @Test
+ public void forceStopOtDaemon_serviceApiTimeout_failedWithTimeoutError() {
+ doNothing().when(mControllerService).forceStopOtDaemonForTest(eq(true), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"force-stop-ot-daemon", "enabled"});
+
+ verify(mControllerService, times(1)).forceStopOtDaemonForTest(eq(true), any());
+ 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/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
index 49b002a..c932ac8 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
@@ -16,44 +16,53 @@
package com.android.server.thread;
+import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE;
import static com.android.server.thread.ThreadPersistentSettings.THREAD_ENABLED;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.doAnswer;
+
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.validateMockitoUsage;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
+import android.net.thread.ThreadConfiguration;
import android.os.PersistableBundle;
import android.util.AtomicFile;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+
import com.android.connectivity.resources.R;
import com.android.server.connectivity.ConnectivityResources;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
+
import org.junit.After;
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.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+
/** Unit tests for {@link ThreadPersistentSettings}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ThreadPersistentSettingsTest {
- @Mock private AtomicFile mAtomicFile;
+ private static final String TEST_COUNTRY_CODE = "CN";
+
@Mock Resources mResources;
@Mock ConnectivityResources mConnectivityResources;
+ private AtomicFile mAtomicFile;
private ThreadPersistentSettings mThreadPersistentSettings;
+ @Rule(order = 0)
+ public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -61,8 +70,7 @@
when(mConnectivityResources.get()).thenReturn(mResources);
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
- FileOutputStream fos = mock(FileOutputStream.class);
- when(mAtomicFile.startWrite()).thenReturn(fos);
+ mAtomicFile = createAtomicFile();
mThreadPersistentSettings =
new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
}
@@ -76,7 +84,7 @@
@Test
public void initialize_readsFromFile() throws Exception {
byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
- setupAtomicFileMockForRead(data);
+ setupAtomicFileForRead(data);
mThreadPersistentSettings.initialize();
@@ -86,7 +94,7 @@
@Test
public void initialize_ThreadDisabledInResources_returnsThreadDisabled() throws Exception {
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
- setupAtomicFileMockForRead(new byte[0]);
+ setupAtomicFileForRead(new byte[0]);
mThreadPersistentSettings.initialize();
@@ -98,7 +106,7 @@
throws Exception {
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
byte[] data = createXmlForParsing(THREAD_ENABLED.key, true);
- setupAtomicFileMockForRead(data);
+ setupAtomicFileForRead(data);
mThreadPersistentSettings.initialize();
@@ -110,9 +118,6 @@
mThreadPersistentSettings.put(THREAD_ENABLED.key, true);
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
}
@Test
@@ -120,9 +125,81 @@
mThreadPersistentSettings.put(THREAD_ENABLED.key, false);
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
+ }
+
+ @Test
+ public void put_ThreadCountryCodeString_returnsString() throws Exception {
+ mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, TEST_COUNTRY_CODE);
+
+ assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
+ }
+
+ @Test
+ public void put_ThreadCountryCodeNull_returnsNull() throws Exception {
+ mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, null);
+
+ assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
+ }
+
+ @Test
+ public void putConfiguration_sameValues_returnsFalse() {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcp6PdEnabled(true)
+ .build();
+ mThreadPersistentSettings.putConfiguration(configuration);
+
+ assertThat(mThreadPersistentSettings.putConfiguration(configuration)).isFalse();
+ }
+
+ @Test
+ public void putConfiguration_differentValues_returnsTrue() {
+ ThreadConfiguration configuration1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(false)
+ .setDhcp6PdEnabled(false)
+ .build();
+ mThreadPersistentSettings.putConfiguration(configuration1);
+ ThreadConfiguration configuration2 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcp6PdEnabled(true)
+ .build();
+
+ assertThat(mThreadPersistentSettings.putConfiguration(configuration2)).isTrue();
+ }
+
+ @Test
+ public void putConfiguration_nat64Enabled_valuesUpdatedAndPersisted() throws Exception {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build();
+ mThreadPersistentSettings.putConfiguration(configuration);
+
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ }
+
+ @Test
+ public void putConfiguration_dhcp6PdEnabled_valuesUpdatedAndPersisted() throws Exception {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder().setDhcp6PdEnabled(true).build();
+ mThreadPersistentSettings.putConfiguration(configuration);
+
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ }
+
+ private AtomicFile createAtomicFile() throws Exception {
+ return new AtomicFile(mTemporaryFolder.newFile());
}
private byte[] createXmlForParsing(String key, Boolean value) throws Exception {
@@ -133,19 +210,9 @@
return outputStream.toByteArray();
}
- private void setupAtomicFileMockForRead(byte[] dataToRead) throws Exception {
- FileInputStream is = mock(FileInputStream.class);
- when(mAtomicFile.openRead()).thenReturn(is);
- when(is.available()).thenReturn(dataToRead.length).thenReturn(0);
- doAnswer(
- invocation -> {
- byte[] data = invocation.getArgument(0);
- int pos = invocation.getArgument(1);
- if (pos == dataToRead.length) return 0; // read complete.
- System.arraycopy(dataToRead, 0, data, 0, dataToRead.length);
- return dataToRead.length;
- })
- .when(is)
- .read(any(), anyInt(), anyInt());
+ private void setupAtomicFileForRead(byte[] dataToRead) throws Exception {
+ try (FileOutputStream outputStream = new FileOutputStream(mAtomicFile.getBaseFile())) {
+ outputStream.write(dataToRead);
+ }
}
}
diff --git a/thread/tests/utils/Android.bp b/thread/tests/utils/Android.bp
index 24e9bb9..726ec9d 100644
--- a/thread/tests/utils/Android.bp
+++ b/thread/tests/utils/Android.bp
@@ -27,6 +27,7 @@
"net-tests-utils",
"net-utils-device-common",
"net-utils-device-common-bpf",
+ "net-utils-device-common-struct-base",
],
srcs: [
"src/**/*.java",
diff --git a/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java b/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
index 43f177d..b586a19 100644
--- a/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
+++ b/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
@@ -62,6 +62,7 @@
private final Looper mLooper;
private TestNetworkInterface mInterface;
private TestableNetworkAgent mAgent;
+ private Network mNetwork;
private final TestableNetworkCallback mNetworkCallback;
private final ConnectivityManager mConnectivityManager;
@@ -91,6 +92,11 @@
return mInterface.getInterfaceName();
}
+ /** Returns the {@link android.net.Network} of the test network. */
+ public Network getNetwork() {
+ return mNetwork;
+ }
+
private void setUpTestNetwork() throws Exception {
mInterface = mContext.getSystemService(TestNetworkManager.class).createTapInterface();
@@ -105,13 +111,13 @@
newNetworkCapabilities(),
lp,
new NetworkAgentConfig.Builder().build());
- final Network network = mAgent.register();
+ mNetwork = mAgent.register();
mAgent.markConnected();
PollingCheck.check(
"No usable address on interface",
TIMEOUT.toMillis(),
- () -> hasUsableAddress(network, getInterfaceName()));
+ () -> hasUsableAddress(mNetwork, getInterfaceName()));
lp.setLinkAddresses(makeLinkAddresses());
mAgent.sendLinkProperties(lp);
diff --git a/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
new file mode 100644
index 0000000..38a6e90
--- /dev/null
+++ b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
@@ -0,0 +1,162 @@
+/*
+ * 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 android.net.thread.utils;
+
+import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A rule used to skip Thread tests when the device doesn't support a specific feature indicated by
+ * {@code ThreadFeatureCheckerRule.Requires*}.
+ */
+public final class ThreadFeatureCheckerRule implements TestRule {
+ private static final String KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED = "5.15.0";
+ private static final int KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED = 14;
+
+ /**
+ * Annotates a test class or method requires the Thread feature to run.
+ *
+ * <p>In Absence of the Thread feature, the test class or method will be ignored.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface RequiresThreadFeature {}
+
+ /**
+ * Annotates a test class or method requires the kernel IPv6 multicast routing feature to run.
+ *
+ * <p>In Absence of the multicast routing feature, the test class or method will be ignored.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface RequiresIpv6MulticastRouting {}
+
+ /**
+ * Annotates a test class or method requires the simulation Thread device (i.e. ot-cli-ftd) to
+ * run.
+ *
+ * <p>In Absence of the simulation device, the test class or method will be ignored.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface RequiresSimulationThreadDevice {}
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ if (hasAnnotation(RequiresThreadFeature.class, description)) {
+ assumeTrue(
+ "Skipping test because the Thread feature is unavailable",
+ hasThreadFeature());
+ }
+
+ if (hasAnnotation(RequiresIpv6MulticastRouting.class, description)) {
+ assumeTrue(
+ "Skipping test because kernel IPv6 multicast routing is unavailable",
+ hasIpv6MulticastRouting());
+ }
+
+ if (hasAnnotation(RequiresSimulationThreadDevice.class, description)) {
+ assumeTrue(
+ "Skipping test because simulation Thread device is unavailable",
+ hasSimulationThreadDevice());
+ }
+
+ base.evaluate();
+ }
+ };
+ }
+
+ /** Returns {@code true} if a test method or the test class is annotated with annotation. */
+ private <T extends Annotation> boolean hasAnnotation(
+ Class<T> annotationClass, Description description) {
+ // Method annotation
+ boolean hasAnnotation = description.getAnnotation(annotationClass) != null;
+
+ // Class annotation
+ Class<?> clazz = description.getTestClass();
+ while (!hasAnnotation && clazz != Object.class) {
+ hasAnnotation |= clazz.getAnnotation(annotationClass) != null;
+ clazz = clazz.getSuperclass();
+ }
+
+ return hasAnnotation;
+ }
+
+ /** Returns {@code true} if this device has the Thread feature supported. */
+ private static boolean hasThreadFeature() {
+ final Context context = ApplicationProvider.getApplicationContext();
+
+ // Use service name rather than `ThreadNetworkManager.class` to avoid
+ // `ClassNotFoundException` on U- devices.
+ return context.getSystemService("thread_network") != null;
+ }
+
+ /**
+ * Returns {@code true} if this device has the kernel IPv6 multicast routing feature enabled.
+ */
+ private static boolean hasIpv6MulticastRouting() {
+ // The kernel IPv6 multicast routing (i.e. IPV6_MROUTE) is enabled on kernel version
+ // android14-5.15.0 and later
+ return isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)
+ && isKernelAndroidVersionAtLeast(
+ KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED);
+ }
+
+ /**
+ * Returns {@code true} if the android version in the kernel version of this device is equal to
+ * or larger than the given {@code minVersion}.
+ */
+ private static boolean isKernelAndroidVersionAtLeast(int minVersion) {
+ final String osRelease = VintfRuntimeInfo.getOsRelease();
+ final Pattern pattern = Pattern.compile("android(\\d+)");
+ Matcher matcher = pattern.matcher(osRelease);
+
+ if (matcher.find()) {
+ int version = Integer.parseInt(matcher.group(1));
+ return (version >= minVersion);
+ }
+ return false;
+ }
+
+ /** Returns {@code true} if the simulation Thread device is supported. */
+ private static boolean hasSimulationThreadDevice() {
+ // Simulation radio is supported on only Cuttlefish
+ return SystemProperties.get("ro.product.model").startsWith("Cuttlefish");
+ }
+}
diff --git a/tools/Android.bp b/tools/Android.bp
index 9216b5b..2c2ed14 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -83,6 +83,8 @@
],
data: [
"testdata/test-jarjar-excludes.txt",
+ // txt with Test classes to test they aren't included when added to jarjar excludes
+ "testdata/test-jarjar-excludes-testclass.txt",
// two unsupportedappusage lists with different classes to test using multiple lists
"testdata/test-unsupportedappusage.txt",
"testdata/test-other-unsupportedappusage.txt",
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
diff --git a/tools/gen_jarjar_test.py b/tools/gen_jarjar_test.py
index f5bf499..12038e9 100644
--- a/tools/gen_jarjar_test.py
+++ b/tools/gen_jarjar_test.py
@@ -84,6 +84,31 @@
'rule test.utils.TestUtilClass$TestInnerClassTest jarjar.prefix.@0\n',
'rule test.utils.TestUtilClass$TestInnerClassTest$* jarjar.prefix.@0\n'], lines)
+ def test_gen_rules_repeated_testclass_excluded(self):
+ args = gen_jarjar.parse_arguments([
+ "jarjar-rules-generator-testjavalib.jar",
+ "--prefix", "jarjar.prefix",
+ "--output", "test-output-rules.txt",
+ "--apistubs", "framework-connectivity.stubs.module_lib.jar",
+ "--unsupportedapi", ":testdata/test-unsupportedappusage.txt",
+ "--excludes", "testdata/test-jarjar-excludes-testclass.txt",
+ ])
+ gen_jarjar.make_jarjar_rules(args)
+
+ with open(args.output) as out:
+ lines = out.readlines()
+
+ self.maxDiff = None
+ self.assertListEqual([
+ 'rule android.net.IpSecTransform jarjar.prefix.@0\n',
+ 'rule test.unsupportedappusage.OtherUnsupportedUsageClass jarjar.prefix.@0\n',
+ 'rule test.unsupportedappusage.OtherUnsupportedUsageClassTest jarjar.prefix.@0\n',
+ 'rule test.unsupportedappusage.OtherUnsupportedUsageClassTest$* jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClass jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClassTest jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClassTest$* jarjar.prefix.@0\n'], lines)
+
if __name__ == '__main__':
# Need verbosity=2 for the test results parser to find results
diff --git a/tools/testdata/test-jarjar-excludes-testclass.txt b/tools/testdata/test-jarjar-excludes-testclass.txt
new file mode 100644
index 0000000..f7cc2cb
--- /dev/null
+++ b/tools/testdata/test-jarjar-excludes-testclass.txt
@@ -0,0 +1,7 @@
+# Test file for excluded classes
+test\.jarj.rexcluded\.JarjarExcludedCla.s
+test\.jarjarexcluded\.JarjarExcludedClass\$TestInnerCl.ss
+
+# Exclude actual test files
+test\.utils\.TestUtilClassTest
+android\.net\.IpSecTransformTest
\ No newline at end of file