Merge "Ignore no permission calls for CaptivePortal.appRequest" into main
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/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index 7612210..b24e3ac 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -3,6 +3,8 @@
# For cherry-picks of CLs that are already merged in aosp/master, or flaky test fixes.
jchalard@google.com #{LAST_RESORT_SUGGESTION}
+# In addition to cherry-picks and flaky test fixes, 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
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 30bdf37..4bae221 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -103,9 +103,9 @@
"dscpPolicy.o",
"netd.o",
"offload.o",
- "offload@btf.o",
+ "offload@mainline.o",
"test.o",
- "test@btf.o",
+ "test@mainline.o",
],
apps: [
"ServiceConnectivityResources",
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/offload.c b/bpf_progs/offload.c
index 90f96a1..dd59dca 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 */
+#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 */
+#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..e2b8ea5 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 */
+#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 */
+#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/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/flags.aconfig b/common/flags.aconfig
index 19b522c..30931df 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -6,6 +6,7 @@
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,6 +78,7 @@
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"
diff --git a/common/nearby_flags.aconfig b/common/nearby_flags.aconfig
index b957d33..b733849 100644
--- a/common/nearby_flags.aconfig
+++ b/common/nearby_flags.aconfig
@@ -3,6 +3,7 @@
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/thread_flags.aconfig b/common/thread_flags.aconfig
index 09595a6..43fc147 100644
--- a/common/thread_flags.aconfig
+++ b/common/thread_flags.aconfig
@@ -3,6 +3,7 @@
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/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/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index f4cc2ac..dba08a1 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -70,7 +70,8 @@
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;
@@ -497,7 +498,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
*/
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index 4de02ac..f7600b2 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -20,7 +20,6 @@
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_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -41,8 +40,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;
@@ -291,18 +288,6 @@
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.
- };
-
private final NetworkCapabilities mNetworkCapabilities;
// A boolean that represents whether the NOT_VCN_MANAGED capability should be deduced when
@@ -318,16 +303,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/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index dfe5867..a80db85 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -84,6 +84,21 @@
@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;
+
private ConnectivityCompatChanges() {
}
}
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index f397b37..c39b46c 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -19,19 +19,15 @@
}
install_symlink {
- name: "platform_ethtool_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",
-}
-phony {
- name: "mainline_tethering_platform_components",
- required: [
- "netbpfload",
- "platform_ethtool_symlink",
- ],
+ init_rc: ["netbpfload.rc"],
+ required: ["bpfloader"],
}
cc_binary {
@@ -65,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
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index ed7d048..0688e88 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -171,8 +171,6 @@
#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) {
char * found_blockdev = NULL;
@@ -232,12 +230,6 @@
ALOGI("NetBpfLoad '%s' starting...", argv[0]);
- // true iff we are running from the module
- const bool is_mainline = !strcmp(argv[0], apexNetBpfLoad);
-
- // true iff we are running from the platform
- const bool is_platform = !strcmp(argv[0], platformNetBpfLoad);
-
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__);
@@ -248,24 +240,11 @@
// 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",
+ ALOGI("NetBpfLoad api:%d/%d kver:%07x rc:%d%d",
android_get_application_target_sdk_version(), device_api_level,
- android::bpf::kernelVersion(), is_platform, is_mainline,
+ android::bpf::kernelVersion(),
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,7 +257,7 @@
logTetheringApexVersion();
- if (is_mainline && has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
+ if (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
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index c534b2c..3026655 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -31,13 +31,13 @@
#include <sys/wait.h>
#include <unistd.h>
-// This is BpfLoader v0.41
+// This is Network BpfLoader v0.42
// 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_MINOR 42u
#define BPFLOADER_VERSION ((BPFLOADER_VERSION_MAJOR << 16) | BPFLOADER_VERSION_MINOR)
#include "BpfSyscallWrappers.h"
@@ -413,9 +413,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) {
@@ -574,6 +571,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),
@@ -711,6 +716,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.
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/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index c162bcc..98c2d86 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
@@ -528,7 +558,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/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index c2363c0..c1c7d5f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -102,12 +102,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);
}
/**
@@ -149,10 +152,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 +238,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);
@@ -283,8 +286,8 @@
mAnnouncer.stop(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 +297,11 @@
} 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);
+ }
+ });
}
}
@@ -330,7 +335,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 +345,6 @@
}
mReplySender.cancelAll();
mSocket.removePacketHandler(this);
- mCbHandler.post(() -> mCb.onDestroyed(mSocket));
}
/**
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/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..7b71e43 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -106,6 +106,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 +119,7 @@
unicastReceiverBuffer = null;
}
this.mdnsFeatureFlags = mdnsFeatureFlags;
+ this.interfaceProvider = new MulticastNetworkInterfaceProvider(context, sharedLog);
}
@Override
@@ -138,6 +140,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 +186,7 @@
}
multicastLock.release();
+ interfaceProvider.stopWatchingConnectivityChanges();
shouldStopSocketLoop = true;
waitForSendThreadToStop();
@@ -482,8 +486,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/net/NetworkStatsObservers.java b/service-t/src/com/android/server/net/NetworkStatsObservers.java
index 21cf351..cab29e3 100644
--- a/service-t/src/com/android/server/net/NetworkStatsObservers.java
+++ b/service-t/src/com/android/server/net/NetworkStatsObservers.java
@@ -32,7 +32,6 @@
import android.net.NetworkTemplate;
import android.net.netstats.IUsageCallback;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -46,6 +45,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.PerUidCounter;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -78,8 +78,11 @@
// Sequence number of DataUsageRequests
private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
- // Lazily instantiated when an observer is registered.
- private volatile Handler mHandler;
+ private final Handler mHandler;
+
+ NetworkStatsObservers(@NonNull Looper looper) {
+ mHandler = new Handler(Objects.requireNonNull(looper), mHandlerCallback);
+ }
/**
* Creates a wrapper that contains the caller context and a normalized request.
@@ -100,7 +103,7 @@
if (LOG) Log.d(TAG, "Registering observer for " + requestInfo);
mDataUsageRequestsPerUid.incrementCountOrThrow(callingUid);
- getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
return request;
}
@@ -110,7 +113,7 @@
* <p>It will unregister the observer asynchronously, so it is safe to call from any thread.
*/
public void unregister(DataUsageRequest request, int callingUid) {
- getHandler().sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
request));
}
@@ -125,34 +128,10 @@
long currentTime) {
StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
activeUidIfaces, currentTime);
- getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
}
- private Handler getHandler() {
- if (mHandler == null) {
- synchronized (this) {
- if (mHandler == null) {
- if (LOGV) Log.v(TAG, "Creating handler");
- mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
- }
- }
- }
- return mHandler;
- }
-
- @VisibleForTesting
- protected Looper getHandlerLooperLocked() {
- // TODO: Currently, callbacks are dispatched on this thread if the caller register
- // callback without supplying a Handler. To ensure that the service handler thread
- // is not blocked by client code, the observers must create their own thread. Once
- // all callbacks are dispatched outside of the handler thread, the service handler
- // thread can be used here.
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- return handlerThread.getLooper();
- }
-
- private Handler.Callback mHandlerCallback = new Handler.Callback() {
+ private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 9684d18..64b17eb 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -593,7 +593,7 @@
INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
alarmManager, wakeLock, getDefaultClock(),
new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
- new NetworkStatsObservers(), new Dependencies());
+ new Dependencies());
return service;
}
@@ -603,8 +603,7 @@
@VisibleForTesting
NetworkStatsService(Context context, INetd netd, AlarmManager alarmManager,
PowerManager.WakeLock wakeLock, Clock clock, NetworkStatsSettings settings,
- NetworkStatsFactory factory, NetworkStatsObservers statsObservers,
- @NonNull Dependencies deps) {
+ NetworkStatsFactory factory, @NonNull Dependencies deps) {
mContext = Objects.requireNonNull(context, "missing Context");
mNetd = Objects.requireNonNull(netd, "missing Netd");
mAlarmManager = Objects.requireNonNull(alarmManager, "missing AlarmManager");
@@ -612,7 +611,6 @@
mSettings = Objects.requireNonNull(settings, "missing NetworkStatsSettings");
mWakeLock = Objects.requireNonNull(wakeLock, "missing WakeLock");
mStatsFactory = Objects.requireNonNull(factory, "missing factory");
- mStatsObservers = Objects.requireNonNull(statsObservers, "missing NetworkStatsObservers");
mDeps = Objects.requireNonNull(deps, "missing Dependencies");
mStatsDir = mDeps.getOrCreateStatsDir();
if (!mStatsDir.exists()) {
@@ -622,6 +620,7 @@
final HandlerThread handlerThread = mDeps.makeHandlerThread();
handlerThread.start();
mHandler = new NetworkStatsHandler(handlerThread.getLooper());
+ mStatsObservers = new NetworkStatsObservers(handlerThread.getLooper());
mNetworkStatsSubscriptionsMonitor = deps.makeSubscriptionsMonitor(mContext,
(command) -> mHandler.post(command) , this);
mContentResolver = mContext.getContentResolver();
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/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index fe3bbbd..d8bc008 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -97,6 +97,8 @@
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.os.Process.INVALID_UID;
import static android.os.Process.VPN_UID;
import static android.system.OsConstants.ETH_P_ALL;
@@ -214,7 +216,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;
@@ -2607,7 +2608,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();
@@ -3020,6 +3021,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();
@@ -7716,10 +7734,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);
@@ -7806,7 +7826,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;
}
@@ -7958,8 +7978,8 @@
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);
@@ -8019,7 +8039,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.
@@ -8052,7 +8072,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);
@@ -12025,7 +12045,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(
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index baff09b..ac922cd 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -39,6 +39,9 @@
// Android U / 14 (api level 34) - various new program types added
#define BPFLOADER_U_VERSION 37u
+// Android V / 15 (api level 35) - this bpfloader should eventually go back to T
+#define BPFLOADER_MAINLINE_VERSION 42u
+
/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
* before #include "bpf_helpers.h" to change which bpfloaders will
* process the resulting .o file.
@@ -111,10 +114,12 @@
#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_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/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 2ca8832..1241e18 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -160,6 +160,10 @@
private static final long BROADCAST_TIMEOUT_MS = 5_000;
+ // Should be kept in sync with the constant in NetworkPolicyManagerService.
+ // TODO: b/322115994 - remove once the feature is in staging.
+ private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false;
+
protected Context mContext;
protected Instrumentation mInstrumentation;
protected ConnectivityManager mCm;
@@ -229,9 +233,8 @@
}
final String output = executeShellCommand("device_config get backstage_power"
+ " com.android.server.net.network_blocked_for_top_sleeping_and_above");
- return Boolean.parseBoolean(output);
+ return Boolean.parseBoolean(output) && ALWAYS_RESTRICT_BACKGROUND_NETWORK;
}
-
protected int getUid(String packageName) throws Exception {
return mContext.getPackageManager().getPackageUid(packageName, 0);
}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index dbececf..8dbcf2f 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -2108,6 +2108,46 @@
}
@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 testServiceTypeClientRemovedAfterSocketDestroyed() {
val si = makeTestServiceInfo(testNetwork1.network)
// Register service on testNetwork1
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..035f607 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -66,6 +66,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 +89,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 +225,7 @@
override fun getSystemProperties() = mock(MockableSystemProperties::class.java)
override fun makeNetIdManager() = TestNetIdManager()
override fun getBpfNetMaps(context: Context?, netd: INetd?) = mock(BpfNetMaps::class.java)
+ override fun isChangeEnabled(changeId: Long, uid: Int) = true
override fun makeMultinetworkPolicyTracker(
c: Context,
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..27c4561 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -240,7 +240,7 @@
AdvertisingRequest capturedRequest = getAdvertisingRequest(
req -> verify(mServiceConn).registerService(anyInt(), req.capture()));
- assertEquals(request, capturedRequest);
+ assertEquals(request.getTtl(), capturedRequest.getTtl());
}
private void doTestRegisterService() throws Exception {
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/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/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index 28608bb..69fec85 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -179,7 +179,7 @@
// 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
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..c69b1e1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -38,6 +38,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
@@ -146,7 +147,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 +170,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])
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/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 8b7ab71..7ced1cb 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,17 @@
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.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,6 +51,7 @@
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -71,6 +75,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 +89,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);
@@ -320,19 +328,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());
}
}
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/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index d7343b1..7007b16 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)
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 3b83c41..1966cb1 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
@@ -42,6 +43,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
@@ -53,7 +55,6 @@
import android.permission.PermissionManager.PermissionResult
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,8 +76,8 @@
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.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.fail
@@ -103,6 +104,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
@@ -176,6 +179,7 @@
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()
}
@@ -263,7 +267,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.
@@ -298,6 +302,19 @@
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
}
inner class CSContext(base: Context) : BroadcastInterceptingContext(base) {
@@ -398,6 +415,7 @@
Context.TELEPHONY_SERVICE -> telephonyManager
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)
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index e62ac74..0bbc34c 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -47,7 +47,6 @@
import android.net.NetworkTemplate;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -127,13 +126,7 @@
mObserverHandlerThread = new HandlerThread("NetworkStatsObserversTest");
mObserverHandlerThread.start();
- final Looper observerLooper = mObserverHandlerThread.getLooper();
- mStatsObservers = new NetworkStatsObservers() {
- @Override
- protected Looper getHandlerLooperLocked() {
- return observerLooper;
- }
- };
+ mStatsObservers = new NetworkStatsObservers(mObserverHandlerThread.getLooper());
mActiveIfaces = new ArrayMap<>();
mActiveUidIfaces = new ArrayMap<>();
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 3ed51bc..3d7ad66 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -123,7 +123,6 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.SimpleClock;
import android.provider.Settings;
@@ -293,7 +292,6 @@
private String mCompareStatsResult = null;
private @Mock Resources mResources;
private Boolean mIsDebuggable;
- private HandlerThread mObserverHandlerThread;
final TestDependencies mDeps = new TestDependencies();
private class MockContext extends BroadcastInterceptingContext {
@@ -377,21 +375,8 @@
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mHandlerThread = new HandlerThread("NetworkStatsServiceTest-HandlerThread");
- // Create a separate thread for observers to run on. This thread cannot be the same
- // as the handler thread, because the observer callback is fired on this thread, and
- // it should not be blocked by client code. Additionally, creating the observers
- // object requires a looper, which can only be obtained after a thread has been started.
- mObserverHandlerThread = new HandlerThread("NetworkStatsServiceTest-ObserversThread");
- mObserverHandlerThread.start();
- final Looper observerLooper = mObserverHandlerThread.getLooper();
- final NetworkStatsObservers statsObservers = new NetworkStatsObservers() {
- @Override
- protected Looper getHandlerLooperLocked() {
- return observerLooper;
- }
- };
mService = new NetworkStatsService(mServiceContext, mNetd, mAlarmManager, wakeLock,
- mClock, mSettings, mStatsFactory, statsObservers, mDeps);
+ mClock, mSettings, mStatsFactory, mDeps);
mElapsedRealtime = 0L;
@@ -589,10 +574,6 @@
mHandlerThread.quitSafely();
mHandlerThread.join();
}
- if (mObserverHandlerThread != null) {
- mObserverHandlerThread.quitSafely();
- mObserverHandlerThread.join();
- }
}
private void initWifiStats(NetworkStateSnapshot snapshot) throws Exception {
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/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 0623b87..1b36d2b 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -489,7 +489,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 +511,7 @@
+ localNetworkInfo
+ "}");
if (localNetworkInfo.getUpstreamNetwork() == null) {
- mUpstreamNetwork = null;
+ disableBorderRouting();
return;
}
if (!localNetworkInfo.getUpstreamNetwork().equals(mUpstreamNetwork)) {
@@ -523,6 +530,7 @@
// requirement.
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
.build(),
new ThreadNetworkCallback(),
mHandler);
@@ -936,23 +944,22 @@
mBorderRouterConfig.isBorderRoutingEnabled = true;
mOtDaemon.configureBorderRouter(
- mBorderRouterConfig,
- new IOtStatusReceiver.Stub() {
- @Override
- public void onSuccess() {
- Log.i(TAG, "configure border router successfully");
- }
+ mBorderRouterConfig, new ConfigureBorderRouterStatusReceiver());
+ } catch (RemoteException | IOException 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 {
+ mOtDaemon.configureBorderRouter(
+ mBorderRouterConfig, new ConfigureBorderRouterStatusReceiver());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to disable border routing", e);
}
}
@@ -1073,6 +1080,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}.
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index d84cd20..88ee47e 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -24,6 +24,7 @@
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.isMulticastRoutingSupported;
import static android.net.thread.utils.IntegrationTestUtils.isSimulatedThreadRadioSupported;
import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination;
import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
@@ -33,7 +34,6 @@
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;
@@ -96,7 +96,6 @@
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 =
@@ -178,7 +177,7 @@
}
@Test
- public void unicastRouting_infraDevicePingTheadDeviceOmr_replyReceived() throws Exception {
+ public void unicastRouting_infraDevicePingThreadDeviceOmr_replyReceived() throws Exception {
/*
* <pre>
* Topology:
@@ -200,6 +199,30 @@
}
@Test
+ public void unicastRouting_afterFactoryResetInfraDevicePingThreadDeviceOmr_replyReceived()
+ throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ // Form the network.
+ mOtCtl.factoryReset();
+ startBrLeader();
+ startInfraDevice();
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+
+ mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
+
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ }
+
+ @Test
public void unicastRouting_borderRouterSendsUdpToThreadDevice_datagramReceived()
throws Exception {
assumeTrue(isSimulatedThreadRadioSupported());
@@ -239,7 +262,7 @@
@Test
public void multicastRouting_ftdSubscribedMulticastAddress_infraLinkJoinsMulticastGroup()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -261,7 +284,7 @@
public void
multicastRouting_ftdSubscribedScope3MulticastAddress_infraLinkNotJoinMulticastGroup()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -282,7 +305,7 @@
@Test
public void multicastRouting_ftdSubscribedMulticastAddress_canPingfromInfraLink()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -304,7 +327,7 @@
@Test
public void multicastRouting_inboundForwarding_afterBrRejoinFtdRepliesSubscribedAddress()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
// 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
@@ -314,7 +337,7 @@
@Test
public void multicastRouting_ftdSubscribedScope3MulticastAddress_cannotPingfromInfraLink()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -336,7 +359,7 @@
@Test
public void multicastRouting_ftdNotSubscribedMulticastAddress_cannotPingFromInfraDevice()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -357,7 +380,7 @@
@Test
public void multicastRouting_multipleFtdsSubscribedDifferentAddresses_canPingFromInfraDevice()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -393,7 +416,7 @@
@Test
public void multicastRouting_multipleFtdsSubscribedSameAddress_canPingFromInfraDevice()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -427,7 +450,7 @@
@Test
public void multicastRouting_outboundForwarding_scopeLargerThan3IsForwarded() throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -453,7 +476,7 @@
@Test
public void multicastRouting_outboundForwarding_scopeSmallerThan4IsNotForwarded()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -475,7 +498,7 @@
@Test
public void multicastRouting_outboundForwarding_llaToScope4IsNotForwarded() throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -498,7 +521,7 @@
@Test
public void multicastRouting_outboundForwarding_mlaToScope4IsNotForwarded() throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -525,7 +548,7 @@
@Test
public void multicastRouting_infraNetworkSwitch_ftdRepliesToSubscribedAddress()
throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -553,7 +576,7 @@
@Test
public void multicastRouting_infraNetworkSwitch_outboundPacketIsForwarded() throws Exception {
- assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
index 5f1f76a..3493d9f 100644
--- a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -46,7 +46,6 @@
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.os.HandlerThread;
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 6e70d24..bb2d973 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -20,6 +20,7 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
@@ -32,6 +33,7 @@
import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
import androidx.annotation.NonNull;
@@ -63,6 +65,8 @@
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/** Static utility methods relating to Thread integration tests. */
public final class IntegrationTestUtils {
@@ -75,6 +79,9 @@
public static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
public static final Duration SERVICE_DISCOVERY_TIMEOUT = Duration.ofSeconds(20);
+ private static final String KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED = "5.15.0";
+ private static final int KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED = 14;
+
private IntegrationTestUtils() {}
/** Returns whether the device supports simulated Thread radio. */
@@ -83,6 +90,24 @@
return SystemProperties.get("ro.product.model").startsWith("Cuttlefish");
}
+ public static boolean isMulticastRoutingSupported() {
+ return isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)
+ && isKernelAndroidVersionAtLeast(
+ KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED);
+ }
+
+ private static boolean isKernelAndroidVersionAtLeast(int n) {
+ 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 >= n);
+ }
+ return false;
+ }
+
/**
* Waits for the given {@link Supplier} to be true until given timeout.
*
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..9406a2f 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
@@ -17,7 +17,9 @@
package com.android.server.thread;
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;
@@ -30,13 +32,13 @@
import android.content.res.Resources;
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.Test;
@@ -44,6 +46,10 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
/** Unit tests for {@link ThreadPersistentSettings}. */
@RunWith(AndroidJUnit4.class)
@SmallTest