Merge "link libbase statically into libnetd_updatable - saves ~85 kB"
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 721f05e..ea3f8d6 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -52,7 +52,7 @@
first: {
jni_libs: [
"libservice-connectivity",
- "libcom_android_connectivity_com_android_net_module_util_jni",
+ "libandroid_net_connectivity_com_android_net_module_util_jni",
],
native_shared_libs: ["libnetd_updatable"],
},
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index abcfbeb..b5aedeb 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -26,7 +26,9 @@
// The above defaults can be used to disable framework-connectivity t
// targets while minimizing merge conflicts in the build rules.
-
+// SDK library for connectivity bootclasspath classes that were part of the non-updatable API before
+// T, and were moved to the module in T. Other bootclasspath classes in connectivity should go to
+// framework-connectivity.
java_sdk_library {
name: "framework-connectivity-tiramisu",
sdk_version: "module_current",
@@ -37,14 +39,40 @@
],
srcs: [
":framework-connectivity-tiramisu-updatable-sources",
+ ":framework-nearby-java-sources",
+ ],
+ // Do not add static_libs to this library: put them in framework-connectivity instead.
+ // The jarjar rules are only so that references to jarjared utils in
+ // framework-connectivity-pre-jarjar match at runtime.
+ jarjar_rules: ":connectivity-jarjar-rules",
+ stub_only_libs: [
+ // Use prebuilt framework-connectivity stubs to avoid circular dependencies
+ "sdk_module-lib_current_framework-connectivity",
],
libs: [
"unsupportedappusage",
"app-compat-annotations",
+ "sdk_module-lib_current_framework-connectivity",
+ ],
+ impl_only_libs: [
+ // The build system will use framework-bluetooth module_current stubs, because
+ // of sdk_version: "module_current" above.
+ "framework-bluetooth",
+ // Compile against the entire implementation of framework-connectivity,
+ // including hidden methods. This is safe because if framework-connectivity-t is
+ // on the bootclasspath (i.e., T), then framework-connectivity is also on the
+ // bootclasspath (because it shipped in S).
+ //
+ // This compiles against the pre-jarjar target so that this code can use
+ // non-jarjard names of widely-used packages such as com.android.net.module.util.
+ "framework-connectivity-pre-jarjar",
],
permitted_packages: [
"android.net",
"android.net.nsd",
+ "android.nearby",
+ "com.android.connectivity",
+ "com.android.nearby",
],
apex_available: [
"com.android.tethering",
@@ -54,10 +82,12 @@
// In preparation for future move
"//packages/modules/Connectivity/apex",
"//packages/modules/Connectivity/service-t",
+ "//packages/modules/Nearby/service",
"//frameworks/base",
// Tests using hidden APIs
"//cts/tests/netlegacy22.api",
+ "//cts/tests/tests/app.usage", // NetworkUsageStatsTest
"//external/sl4a:__subpackages__",
"//frameworks/libs/net/common/testutils",
"//frameworks/libs/net/common/tests:__subpackages__",
@@ -66,6 +96,7 @@
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/NetworkStack/tests:__subpackages__",
+ "//packages/modules/Nearby/tests:__subpackages__",
"//packages/modules/Wifi/service/tests/wifitests",
],
}
diff --git a/framework/Android.bp b/framework/Android.bp
index de505c7..d3e46fa 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -55,12 +55,11 @@
],
}
-java_sdk_library {
- name: "framework-connectivity",
+java_defaults {
+ name: "framework-connectivity-defaults",
+ defaults: ["framework-module-defaults"],
sdk_version: "module_current",
min_sdk_version: "30",
- defaults: ["framework-module-defaults"],
- installable: true,
srcs: [
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
@@ -76,6 +75,9 @@
"frameworks/native/aidl/binder", // For PersistableBundle.aidl
],
},
+ stub_only_libs: [
+ "framework-connectivity-tiramisu.stubs.module_lib",
+ ],
impl_only_libs: [
"framework-tethering.stubs.module_lib",
"framework-wifi.stubs.module_lib",
@@ -83,16 +85,42 @@
],
static_libs: [
"modules-utils-build",
+ "modules-utils-preconditions",
],
libs: [
+ "framework-connectivity-tiramisu.stubs.module_lib",
"unsupportedappusage",
],
- jarjar_rules: "jarjar-rules.txt",
+ apex_available: [
+ "com.android.tethering",
+ ],
+ lint: { strict_updatability_linting: true },
+}
+
+java_library {
+ name: "framework-connectivity-pre-jarjar",
+ defaults: ["framework-connectivity-defaults"],
+ libs: [
+ // This cannot be in the defaults clause above because if it were, it would be used
+ // to generate the connectivity stubs. That would create a circular dependency
+ // because the tethering stubs depend on the connectivity stubs (e.g.,
+ // TetheringRequest depends on LinkAddress).
+ "framework-tethering.stubs.module_lib",
+ ],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"]
+}
+
+java_sdk_library {
+ name: "framework-connectivity",
+ defaults: ["framework-connectivity-defaults"],
+ installable: true,
+ jarjar_rules: ":connectivity-jarjar-rules",
permitted_packages: ["android.net"],
impl_library_visibility: [
"//packages/modules/Connectivity/Tethering/apex",
// In preparation for future move
"//packages/modules/Connectivity/apex",
+ "//packages/modules/Connectivity/framework-t",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service-t",
"//frameworks/base/packages/Connectivity/service",
@@ -100,6 +128,7 @@
// Tests using hidden APIs
"//cts/tests/netlegacy22.api",
+ "//cts/tests/tests/app.usage", // NetworkUsageStatsTest
"//external/sl4a:__subpackages__",
"//frameworks/base/packages/Connectivity/tests:__subpackages__",
"//frameworks/libs/net/common/testutils",
@@ -111,10 +140,6 @@
"//packages/modules/NetworkStack/tests:__subpackages__",
"//packages/modules/Wifi/service/tests/wifitests",
],
- apex_available: [
- "com.android.tethering",
- ],
- lint: { strict_updatability_linting: true },
}
cc_library_shared {
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 5cfab5d..5961e72 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -43,9 +43,11 @@
field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
+ field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20
field public static final int BLOCKED_REASON_NONE = 0; // 0x0
field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1
+ field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5
field public static final int FIREWALL_CHAIN_POWERSAVE = 3; // 0x3
field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt
deleted file mode 100644
index ae6b9c3..0000000
--- a/framework/jarjar-rules.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
-rule com.android.modules.utils.** android.net.connectivity.framework.modules.utils.@1
-rule android.net.NetworkFactory* android.net.connectivity.framework.NetworkFactory@1
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 7593482..92ede91 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -877,6 +877,15 @@
public static final int BLOCKED_REASON_LOCKDOWN_VPN = 1 << 4;
/**
+ * Flag to indicate that an app is subject to Low Power Standby restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 1 << 5;
+
+ /**
* Flag to indicate that an app is subject to Data saver restrictions that would
* result in its metered network access being blocked.
*
@@ -914,6 +923,7 @@
BLOCKED_REASON_APP_STANDBY,
BLOCKED_REASON_RESTRICTED_MODE,
BLOCKED_REASON_LOCKDOWN_VPN,
+ BLOCKED_REASON_LOW_POWER_STANDBY,
BLOCKED_METERED_REASON_DATA_SAVER,
BLOCKED_METERED_REASON_USER_RESTRICTED,
BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -963,13 +973,22 @@
@SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_RESTRICTED = 4;
+ /**
+ * Firewall chain used for low power standby.
+ * Allowlist of apps that have access in low power standby.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
- FIREWALL_CHAIN_RESTRICTED
+ FIREWALL_CHAIN_RESTRICTED,
+ FIREWALL_CHAIN_LOW_POWER_STANDBY
})
public @interface FirewallChain {}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 4254a66..feb9fc1 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -621,22 +621,22 @@
* Network capabilities that are expected to be mutable, i.e., can change while a particular
* network is connected.
*/
- private static final long MUTABLE_CAPABILITIES =
+ private static final long MUTABLE_CAPABILITIES = NetworkCapabilitiesUtils.packBitList(
// TRUSTED can change when user explicitly connects to an untrusted network in Settings.
// http://b/18206275
- (1 << NET_CAPABILITY_TRUSTED)
- | (1 << NET_CAPABILITY_VALIDATED)
- | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
- | (1 << NET_CAPABILITY_NOT_ROAMING)
- | (1 << NET_CAPABILITY_FOREGROUND)
- | (1 << NET_CAPABILITY_NOT_CONGESTED)
- | (1 << NET_CAPABILITY_NOT_SUSPENDED)
- | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY)
- | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
- | (1 << NET_CAPABILITY_NOT_VCN_MANAGED)
+ NET_CAPABILITY_TRUSTED,
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_NOT_ROAMING,
+ NET_CAPABILITY_FOREGROUND,
+ NET_CAPABILITY_NOT_CONGESTED,
+ NET_CAPABILITY_NOT_SUSPENDED,
+ NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+ NET_CAPABILITY_NOT_VCN_MANAGED,
// The value of NET_CAPABILITY_HEAD_UNIT is 32, which cannot use int to do bit shift,
// otherwise there will be an overflow. Use long to do bit shift instead.
- | (1L << NET_CAPABILITY_HEAD_UNIT);
+ NET_CAPABILITY_HEAD_UNIT);
/**
* Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -650,25 +650,26 @@
// in an infinite loop about these.
private static final long NON_REQUESTABLE_CAPABILITIES =
MUTABLE_CAPABILITIES
- & ~(1 << NET_CAPABILITY_TRUSTED)
- & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED);
+ & ~(1L << NET_CAPABILITY_TRUSTED)
+ & ~(1L << NET_CAPABILITY_NOT_VCN_MANAGED);
/**
* Capabilities that are set by default when the object is constructed.
*/
- private static final long DEFAULT_CAPABILITIES =
- (1 << NET_CAPABILITY_NOT_RESTRICTED)
- | (1 << NET_CAPABILITY_TRUSTED)
- | (1 << NET_CAPABILITY_NOT_VPN);
+ private static final long DEFAULT_CAPABILITIES = NetworkCapabilitiesUtils.packBitList(
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_TRUSTED,
+ NET_CAPABILITY_NOT_VPN);
/**
* Capabilities that are managed by ConnectivityService.
*/
private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
- (1 << NET_CAPABILITY_VALIDATED)
- | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
- | (1 << NET_CAPABILITY_FOREGROUND)
- | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+ NetworkCapabilitiesUtils.packBitList(
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_FOREGROUND,
+ NET_CAPABILITY_PARTIAL_CONNECTIVITY);
/**
* Capabilities that are allowed for test networks. This list must be set so that it is safe
@@ -677,14 +678,15 @@
* INTERNET, IMS, SUPL, etc.
*/
private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
- (1 << NET_CAPABILITY_NOT_METERED)
- | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
- | (1 << NET_CAPABILITY_NOT_RESTRICTED)
- | (1 << NET_CAPABILITY_NOT_VPN)
- | (1 << NET_CAPABILITY_NOT_ROAMING)
- | (1 << NET_CAPABILITY_NOT_CONGESTED)
- | (1 << NET_CAPABILITY_NOT_SUSPENDED)
- | (1 << NET_CAPABILITY_NOT_VCN_MANAGED);
+ NetworkCapabilitiesUtils.packBitList(
+ NET_CAPABILITY_NOT_METERED,
+ NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_NOT_ROAMING,
+ NET_CAPABILITY_NOT_CONGESTED,
+ NET_CAPABILITY_NOT_SUSPENDED,
+ NET_CAPABILITY_NOT_VCN_MANAGED);
/**
* Adds the given capability to this {@code NetworkCapability} instance.
@@ -1156,12 +1158,13 @@
/**
* Allowed transports on an unrestricted test network (in addition to TRANSPORT_TEST).
*/
- private static final int UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
- 1 << TRANSPORT_TEST
- // Test ethernet networks can be created with EthernetManager#setIncludeTestInterfaces
- | 1 << TRANSPORT_ETHERNET
- // Test VPN networks can be created but their UID ranges must be empty.
- | 1 << TRANSPORT_VPN;
+ private static final long UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
+ NetworkCapabilitiesUtils.packBitList(
+ TRANSPORT_TEST,
+ // Test eth networks are created with EthernetManager#setIncludeTestInterfaces
+ TRANSPORT_ETHERNET,
+ // Test VPN networks can be created but their UID ranges must be empty.
+ TRANSPORT_VPN);
/**
* Adds the given transport type to this {@code NetworkCapability} instance.
@@ -3076,4 +3079,4 @@
return new NetworkCapabilities(mCaps);
}
}
-}
+}
\ No newline at end of file
diff --git a/nearby/Android.bp b/nearby/Android.bp
new file mode 100644
index 0000000..baa0740
--- /dev/null
+++ b/nearby/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Empty sources and libraries to avoid merge conflicts with downstream
+// branches
+// TODO: remove once the Nearby sources are available in this branch
+filegroup {
+ name: "framework-nearby-java-sources",
+ srcs: [],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+
+java_library {
+ name: "service-nearby",
+ srcs: [],
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ apex_available: ["com.android.tethering"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 48c74c6..f33be63 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -36,15 +36,17 @@
],
libs: [
"framework-annotations-lib",
- "framework-connectivity.impl",
+ "framework-connectivity-pre-jarjar",
"framework-connectivity-tiramisu.impl",
"service-connectivity-pre-jarjar",
"unsupportedappusage",
],
static_libs: [
- "modules-utils-build",
+ // Do not add static_libs here if they are already included in framework-connectivity
+ // or in service-connectivity. They are not necessary (included via
+ // service-connectivity-pre-jarjar), and in the case of code that is already in
+ // framework-connectivity, the classes would be included in the apex twice.
"modules-utils-statemachine",
- "net-utils-framework-common",
],
apex_available: [
"com.android.tethering",
diff --git a/service/Android.bp b/service/Android.bp
index 1e66c11..6d187ba 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -20,9 +20,9 @@
}
// The library name match the service-connectivity jarjar rules that put the JNI utils in the
-// com.android.connectivity.com.android.net.module.util package.
+// android.net.connectivity.com.android.net.module.util package.
cc_library_shared {
- name: "libcom_android_connectivity_com_android_net_module_util_jni",
+ name: "libandroid_net_connectivity_com_android_net_module_util_jni",
min_sdk_version: "30",
cflags: [
"-Wall",
@@ -98,20 +98,20 @@
],
libs: [
"framework-annotations-lib",
- "framework-connectivity.impl",
+ "framework-connectivity-pre-jarjar",
"framework-tethering.stubs.module_lib",
"framework-wifi.stubs.module_lib",
"unsupportedappusage",
"ServiceConnectivityResources",
],
static_libs: [
+ // Do not add libs here if they are already included
+ // in framework-connectivity
"dnsresolver_aidl_interface-V9-java",
- "modules-utils-build",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-bpf",
"net-utils-device-common-netlink",
- "net-utils-framework-common",
"netd-client",
"networkstack-client",
"PlatformProperties",
@@ -157,8 +157,9 @@
static_libs: [
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
+ "service-nearby",
],
- jarjar_rules: "jarjar-rules.txt",
+ jarjar_rules: ":connectivity-jarjar-rules",
apex_available: [
"com.android.tethering",
],
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index f658a5e..e3b26fd 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -1,6 +1,15 @@
+# Classes in framework-connectivity are restricted to the android.net package.
+# This cannot be changed because it is harcoded in ART in S.
+# Any missing jarjar rule for framework-connectivity would be caught by the
+# build as an unexpected class outside of the android.net package.
+rule com.android.net.module.util.** android.net.connectivity.@0
+rule com.android.modules.utils.** android.net.connectivity.@0
+rule android.net.NetworkFactory* android.net.connectivity.@0
+
+# From modules-utils-preconditions
+rule com.android.internal.util.Preconditions* android.net.connectivity.@0
+
rule android.sysprop.** com.android.connectivity.@0
-rule com.android.net.module.util.** com.android.connectivity.@0
-rule com.android.modules.utils.** com.android.connectivity.@0
# internal util classes from framework-connectivity-shared-srcs
rule android.util.LocalLog* com.android.connectivity.@0
@@ -23,9 +32,6 @@
rule android.net.ResolverParamsParcel* com.android.connectivity.@0
# Also includes netd event listener AIDL, but this is handled by netd-client rules
-# From net-utils-device-common
-rule android.net.NetworkFactory* com.android.connectivity.@0
-
# From netd-client (newer AIDLs should go to android.net.netd.aidl)
rule android.net.netd.aidl.** com.android.connectivity.@0
# Avoid including android.net.INetdEventCallback, used in tests but not part of the module
diff --git a/service/jni/com_android_net_module_util/onload.cpp b/service/jni/com_android_net_module_util/onload.cpp
index 07ae31c..2f09e55 100644
--- a/service/jni/com_android_net_module_util/onload.cpp
+++ b/service/jni/com_android_net_module_util/onload.cpp
@@ -30,10 +30,10 @@
}
if (register_com_android_net_module_util_BpfMap(env,
- "com/android/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
+ "android/net/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
if (register_com_android_net_module_util_TcUtils(env,
- "com/android/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
+ "android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
return JNI_VERSION_1_6;
}
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index bde52a5..2aaa4c3 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -39,10 +39,9 @@
namespace android {
static void native_init(JNIEnv* env, jobject clazz) {
- // start is still being called by netd
- Status status = mTc.initMaps();
+ Status status = mTc.start();
if (!isOk(status)) {
- ALOGE("%s failed", __func__);
+ ALOGE("%s failed, error code = %d", __func__, status.code());
}
}
@@ -51,7 +50,7 @@
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
if (!isOk(status)) {
- ALOGE("%s failed, errer code = %d", __func__, status.code());
+ ALOGE("%s failed, error code = %d", __func__, status.code());
}
return (jint)status.code();
}
@@ -61,7 +60,7 @@
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
if (!isOk(status)) {
- ALOGE("%s failed, errer code = %d", __func__, status.code());
+ ALOGE("%s failed, error code = %d", __func__, status.code());
}
return (jint)status.code();
}
@@ -71,7 +70,7 @@
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
if (!isOk(status)) {
- ALOGE("%s failed, errer code = %d", __func__, status.code());
+ ALOGE("%s failed, error code = %d", __func__, status.code());
}
return (jint)status.code();
}
@@ -81,7 +80,7 @@
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
if (!isOk(status)) {
- ALOGD("%s failed, errer code = %d", __func__, status.code());
+ ALOGD("%s failed, error code = %d", __func__, status.code());
}
return (jint)status.code();
}
diff --git a/service/native/Android.bp b/service/native/Android.bp
index 0e805e2..a20c54f 100644
--- a/service/native/Android.bp
+++ b/service/native/Android.bp
@@ -70,6 +70,7 @@
"libnetdutils",
"libtraffic_controller",
"libutils",
+ "libnetd_updatable",
"netd_aidl_interface-lateststable-ndk",
],
}
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index f401636..39f3365 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -38,6 +38,7 @@
#include "TrafficController.h"
#include "bpf/BpfUtils.h"
+#include "NetdUpdatablePublic.h"
using namespace android::bpf; // NOLINT(google-build-using-namespace): grandfathered
@@ -746,5 +747,133 @@
expectPrivilegedUserSetEmpty();
}
+constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
+constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;
+
+using android::base::Error;
+using android::base::Result;
+using android::bpf::BpfMap;
+
+// This test set up a SkDestroyListener that is running parallel with the production
+// SkDestroyListener. The test will create thousands of sockets and tag them on the
+// production cookieUidTagMap and close them in a short time. When the number of
+// sockets get closed exceeds the buffer size, it will start to return ENOBUFF
+// error. The error will be ignored by the production SkDestroyListener and the
+// test will clean up the tags in tearDown if there is any remains.
+
+// TODO: Instead of test the ENOBUFF error, we can test the production
+// SkDestroyListener to see if it failed to delete a tagged socket when ENOBUFF
+// triggered.
+class NetlinkListenerTest : public testing::Test {
+ protected:
+ NetlinkListenerTest() {}
+ BpfMap<uint64_t, UidTagValue> mCookieTagMap;
+
+ void SetUp() {
+ mCookieTagMap.reset(android::bpf::mapRetrieveRW(COOKIE_TAG_MAP_PATH));
+ ASSERT_TRUE(mCookieTagMap.isValid());
+ }
+
+ void TearDown() {
+ const auto deleteTestCookieEntries = [](const uint64_t& key, const UidTagValue& value,
+ BpfMap<uint64_t, UidTagValue>& map) {
+ if ((value.uid == TEST_UID) && (value.tag == TEST_TAG)) {
+ Result<void> res = map.deleteValue(key);
+ if (res.ok() || (res.error().code() == ENOENT)) {
+ return Result<void>();
+ }
+ ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+ strerror(res.error().code()));
+ }
+ // Move forward to next cookie in the map.
+ return Result<void>();
+ };
+ EXPECT_RESULT_OK(mCookieTagMap.iterateWithValue(deleteTestCookieEntries));
+ }
+
+ Result<void> checkNoGarbageTagsExist() {
+ const auto checkGarbageTags = [](const uint64_t&, const UidTagValue& value,
+ const BpfMap<uint64_t, UidTagValue>&) -> Result<void> {
+ if ((TEST_UID == value.uid) && (TEST_TAG == value.tag)) {
+ return Error(EUCLEAN) << "Closed socket is not untagged";
+ }
+ return {};
+ };
+ return mCookieTagMap.iterateWithValue(checkGarbageTags);
+ }
+
+ bool checkMassiveSocketDestroy(int totalNumber, bool expectError) {
+ std::unique_ptr<android::netdutils::NetlinkListenerInterface> skDestroyListener;
+ auto result = android::net::TrafficController::makeSkDestroyListener();
+ if (!isOk(result)) {
+ ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
+ } else {
+ skDestroyListener = std::move(result.value());
+ }
+ int rxErrorCount = 0;
+ // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
+ const auto rxErrorHandler = [&rxErrorCount](const int, const int) { rxErrorCount++; };
+ skDestroyListener->registerSkErrorHandler(rxErrorHandler);
+ int fds[totalNumber];
+ for (int i = 0; i < totalNumber; i++) {
+ fds[i] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ // The likely reason for a failure is running out of available file descriptors.
+ EXPECT_LE(0, fds[i]) << i << " of " << totalNumber;
+ if (fds[i] < 0) {
+ // EXPECT_LE already failed above, so test case is a failure, but we don't
+ // want potentially tens of thousands of extra failures creating and then
+ // closing all these fds cluttering up the logs.
+ totalNumber = i;
+ break;
+ };
+ libnetd_updatable_tagSocket(fds[i], TEST_TAG, TEST_UID, 1000);
+ }
+
+ // TODO: Use a separate thread that has its own fd table so we can
+ // close sockets even faster simply by terminating that thread.
+ for (int i = 0; i < totalNumber; i++) {
+ EXPECT_EQ(0, close(fds[i]));
+ }
+ // wait a bit for netlink listener to handle all the messages.
+ usleep(SOCK_CLOSE_WAIT_US);
+ if (expectError) {
+ // If ENOBUFS triggered, check it only called into the handler once, ie.
+ // that the netlink handler is not spinning.
+ int currentErrorCount = rxErrorCount;
+ // 0 error count is acceptable because the system has chances to close all sockets
+ // normally.
+ EXPECT_LE(0, rxErrorCount);
+ if (!rxErrorCount) return true;
+
+ usleep(ENOBUFS_POLL_WAIT_US);
+ EXPECT_EQ(currentErrorCount, rxErrorCount);
+ } else {
+ EXPECT_RESULT_OK(checkNoGarbageTagsExist());
+ EXPECT_EQ(0, rxErrorCount);
+ }
+ return false;
+ }
+};
+
+TEST_F(NetlinkListenerTest, TestAllSocketUntagged) {
+ checkMassiveSocketDestroy(10, false);
+ checkMassiveSocketDestroy(100, false);
+}
+
+// Disabled because flaky on blueline-userdebug; this test relies on the main thread
+// winning a race against the NetlinkListener::run() thread. There's no way to ensure
+// things will be scheduled the same way across all architectures and test environments.
+TEST_F(NetlinkListenerTest, DISABLED_TestSkDestroyError) {
+ bool needRetry = false;
+ int retryCount = 0;
+ do {
+ needRetry = checkMassiveSocketDestroy(32500, true);
+ if (needRetry) retryCount++;
+ } while (needRetry && retryCount < 3);
+ // Should review test if it can always close all sockets correctly.
+ EXPECT_GT(3, retryCount);
+}
+
+
} // namespace net
} // namespace android
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index 2d89344..ddcf445 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -32,22 +32,14 @@
using netdutils::StatusOr;
class TrafficController {
- // TODO: marking this private for right now, as start is already called by
- // netd. start() calls initMaps(), initPrograms(), and sets up the socket
- // destroy listener. Both initPrograms() and setting up the socket destroy
- // listener should only be done once.
+ public:
+ static constexpr char DUMP_KEYWORD[] = "trafficcontroller";
+
/*
* Initialize the whole controller
*/
netdutils::Status start();
- public:
- static constexpr char DUMP_KEYWORD[] = "trafficcontroller";
-
- // TODO: marking this public for right now, as start() is already called by
- // netd.
- netdutils::Status initMaps() EXCLUDES(mMutex);
-
int setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
/*
@@ -195,6 +187,8 @@
std::mutex mMutex;
+ netdutils::Status initMaps() EXCLUDES(mMutex);
+
// Keep track of uids that have permission UPDATE_DEVICE_STATS so we don't
// need to call back to system server for permission check.
std::set<uid_t> mPrivilegedUser GUARDED_BY(mMutex);
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index e444a12..7a3bab3 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -22,6 +22,8 @@
import android.system.Os;
import android.util.Log;
+import com.android.modules.utils.build.SdkLevel;
+
/**
* BpfNetMaps is responsible for providing traffic controller relevant functionality.
*
@@ -30,134 +32,118 @@
public class BpfNetMaps {
private static final String TAG = "BpfNetMaps";
private final INetd mNetd;
- // TODO: change USE_JNI to SdkLevel.isAtLeastT()
- private static final boolean USE_JNI = false;
+ // Use legacy netd for releases before T.
+ private static final boolean USE_NETD = !SdkLevel.isAtLeastT();
+ private static boolean sInitialized = false;
- static {
- if (USE_JNI) {
- System.loadLibrary("traffic_controller_jni");
+ /**
+ * Initializes the class if it is not already initialized. This method will open maps but not
+ * cause any other effects. This method may be called multiple times on any thread.
+ */
+ private static synchronized void ensureInitialized() {
+ if (sInitialized) return;
+ if (!USE_NETD) {
+ System.loadLibrary("service-connectivity");
native_init();
}
+ sInitialized = true;
}
public BpfNetMaps(INetd netd) {
+ ensureInitialized();
mNetd = netd;
}
- /**
- * Add naughty app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void addNaughtyApp(final int uid) {
- if (!USE_JNI) {
- try {
- mNetd.bandwidthAddNaughtyApp(uid);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
+ private void maybeThrow(final int err, final String msg) {
+ if (err != 0) {
+ throw new ServiceSpecificException(err, msg + ": " + Os.strerror(err));
+ }
+ }
+
+ /**
+ * Add naughty app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addNaughtyApp(final int uid) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.bandwidthAddNaughtyApp(uid);
return;
}
final int err = native_addNaughtyApp(uid);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to add naughty app: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to add naughty app");
}
- /**
- * Remove naughty app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void removeNaughtyApp(final int uid) {
- if (!USE_JNI) {
- try {
- mNetd.bandwidthRemoveNaughtyApp(uid);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
+ /**
+ * Remove naughty app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeNaughtyApp(final int uid) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.bandwidthRemoveNaughtyApp(uid);
return;
}
final int err = native_removeNaughtyApp(uid);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to remove naughty app: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to remove naughty app");
}
- /**
- * Add nice app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void addNiceApp(final int uid) {
- if (!USE_JNI) {
- try {
- mNetd.bandwidthAddNiceApp(uid);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
+ /**
+ * Add nice app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addNiceApp(final int uid) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.bandwidthAddNiceApp(uid);
return;
}
final int err = native_addNiceApp(uid);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to add nice app: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to add nice app");
}
- /**
- * Remove nice app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void removeNiceApp(final int uid) {
- if (!USE_JNI) {
- try {
- mNetd.bandwidthRemoveNiceApp(uid);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
+ /**
+ * Remove nice app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeNiceApp(final int uid) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.bandwidthRemoveNiceApp(uid);
return;
}
final int err = native_removeNiceApp(uid);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to remove nice app: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to remove nice app");
}
- /**
- * Set target firewall child chain
- *
- * @param childChain target chain to enable
- * @param enable whether to enable or disable child chain.
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void setChildChain(final int childChain, final boolean enable) {
- if (!USE_JNI) {
- try {
- mNetd.firewallEnableChildChain(childChain, enable);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
+ /**
+ * Set target firewall child chain
+ *
+ * @param childChain target chain to enable
+ * @param enable whether to enable or disable child chain.
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void setChildChain(final int childChain, final boolean enable) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallEnableChildChain(childChain, enable);
return;
}
final int err = native_setChildChain(childChain, enable);
- if (err != 0) {
- throw new ServiceSpecificException(-err, "Unable to set child chain: "
- + Os.strerror(-err));
- }
+ maybeThrow(err, "Unable to set child chain");
}
/**
@@ -165,22 +151,19 @@
*
* The chain may be an allowlist chain or a denylist chain. A denylist chain contains DROP
* rules for the specified UIDs and a RETURN rule at the end. An allowlist chain contains RETURN
- * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for for the specified
+ * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for the specified
* UIDs, and a DROP rule at the end. The chain will be created if it does not exist.
*
- * @param chainName The name of the chain to replace.
+ * @param chainName The name of the chain to replace.
* @param isAllowlist Whether this is an allowlist or denylist chain.
- * @param uids The list of UIDs to allow/deny.
- * @return true if the chain was successfully replaced, false otherwise.
+ * @param uids The list of UIDs to allow/deny.
+ * @return 0 if the chain was successfully replaced, errno otherwise.
+ * @throws RemoteException when netd has crashed.
*/
public int replaceUidChain(final String chainName, final boolean isAllowlist,
- final int[] uids) {
- if (!USE_JNI) {
- try {
- mNetd.firewallReplaceUidChain(chainName, isAllowlist, uids);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
+ final int[] uids) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallReplaceUidChain(chainName, isAllowlist, uids);
return 0;
}
final int err = native_replaceUidChain(chainName, isAllowlist, uids);
@@ -190,29 +173,24 @@
return -err;
}
- /**
- * Set firewall rule for uid
- *
- * @param childChain target chain
- * @param uid uid to allow/deny
- * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void setUidRule(final int childChain, final int uid, final int firewallRule) {
- if (!USE_JNI) {
- try {
- mNetd.firewallSetUidRule(childChain, uid, firewallRule);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
+ /**
+ * Set firewall rule for uid
+ *
+ * @param childChain target chain
+ * @param uid uid to allow/deny
+ * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void setUidRule(final int childChain, final int uid, final int firewallRule)
+ throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallSetUidRule(childChain, uid, firewallRule);
return;
}
final int err = native_setUidRule(childChain, uid, firewallRule);
- if (err != 0) {
- throw new ServiceSpecificException(-err, "Unable to set uid rule: "
- + Os.strerror(-err));
- }
+ maybeThrow(err, "Unable to set uid rule");
}
/**
@@ -226,25 +204,19 @@
* instead. Otherwise calling this method will not affect existing rules set on other UIDs.
*
* @param ifName the name of the interface on which the filtering rules will allow packets to
- be received.
- * @param uids an array of UIDs which the filtering rules will be set
+ * be received.
+ * @param uids an array of UIDs which the filtering rules will be set
+ * @throws RemoteException when netd has crashed.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
+ * cause of the failure.
*/
- public void addUidInterfaceRules(final String ifName, final int[] uids) {
- if (!USE_JNI) {
- try {
- mNetd.firewallAddUidInterfaceRules(ifName, uids);
- } catch (RemoteException e) {
- Log.e(TAG, "Exception when updating permissions: " + e);
- }
+ public void addUidInterfaceRules(final String ifName, final int[] uids) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallAddUidInterfaceRules(ifName, uids);
return;
}
final int err = native_addUidInterfaceRules(ifName, uids);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to add uid interface rules: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to add uid interface rules");
}
/**
@@ -254,62 +226,48 @@
* by addUidInterfaceRules(). Ignore any uid which does not have filtering rule.
*
* @param uids an array of UIDs from which the filtering rules will be removed
+ * @throws RemoteException when netd has crashed.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
+ * cause of the failure.
*/
- public void removeUidInterfaceRules(final int[] uids) {
- if (!USE_JNI) {
- try {
- mNetd.firewallRemoveUidInterfaceRules(uids);
- } catch (RemoteException e) {
- Log.e(TAG, "Exception when updating permissions: " + e);
- }
+ public void removeUidInterfaceRules(final int[] uids) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallRemoveUidInterfaceRules(uids);
return;
}
final int err = native_removeUidInterfaceRules(uids);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to remove uid interface rules: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to remove uid interface rules");
}
- /**
- * Request netd to change the current active network stats map.
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void swapActiveStatsMap() {
- if (!USE_JNI) {
- try {
- mNetd.trafficSwapActiveStatsMap();
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
+ /**
+ * Request netd to change the current active network stats map.
+ *
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void swapActiveStatsMap() throws RemoteException {
+ if (USE_NETD) {
+ mNetd.trafficSwapActiveStatsMap();
return;
}
final int err = native_swapActiveStatsMap();
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to swap active stats map: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to swap active stats map");
}
- /**
- * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
- * specified. Or remove all permissions from the uids.
- *
- * @param permission The permission to grant, it could be either PERMISSION_INTERNET and/or
- * PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
- * revoke all permissions for the uids.
- * @param uids uid of users to grant permission
- */
- public void setNetPermForUids(final int permissions, final int[] uids) {
- if (!USE_JNI) {
- try {
- mNetd.trafficSetNetPermForUids(permissions, uids);
- } catch (RemoteException e) {
- Log.e(TAG, "Pass appId list of special permission failed." + e);
- }
+ /**
+ * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
+ * specified. Or remove all permissions from the uids.
+ *
+ * @param permissions The permission to grant, it could be either PERMISSION_INTERNET and/or
+ * PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
+ * revoke all permissions for the uids.
+ * @param uids uid of users to grant permission
+ * @throws RemoteException when netd has crashed.
+ */
+ public void setNetPermForUids(final int permissions, final int[] uids) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.trafficSetNetPermForUids(permissions, uids);
return;
}
native_setPermissionForUids(permissions, uids);
@@ -319,27 +277,26 @@
* Set counter set for uid
*
* @param counterSet either SET_DEFAULT or SET_FOREGROUND
- * @param uid uid to foreground/background
+ * @param uid uid to foreground/background
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
*/
- public int setCounterSet(final int counterSet, final int uid) {
+ public void setCounterSet(final int counterSet, final int uid) {
final int err = native_setCounterSet(counterSet, uid);
- if (err != 0) {
- Log.e(TAG, "setCounterSet failed: " + Os.strerror(-err));
- }
- return -err;
+ maybeThrow(err, "setCounterSet failed");
}
/**
* Reset Uid stats
+ *
* @param tag default 0
* @param uid given uid to be clear
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
*/
- public int deleteTagData(final int tag, final int uid) {
+ public void deleteTagData(final int tag, final int uid) {
final int err = native_deleteTagData(tag, uid);
- if (err != 0) {
- Log.e(TAG, "deleteTagData failed: " + Os.strerror(-err));
- }
- return -err;
+ maybeThrow(err, "deleteTagData failed");
}
private static native void native_init();
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a453270..a9a06e4 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1339,6 +1339,18 @@
}
/**
+ * @see CarrierPrivilegeAuthenticator
+ */
+ public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
+ @NonNull final Context context, @NonNull final TelephonyManager tm) {
+ if (SdkLevel.isAtLeastT()) {
+ return new CarrierPrivilegeAuthenticator(context, tm);
+ } else {
+ return null;
+ }
+ }
+
+ /**
* @see DeviceConfigUtils#isFeatureEnabled
*/
public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
@@ -1426,12 +1438,8 @@
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
- if (SdkLevel.isAtLeastT()) {
- mCarrierPrivilegeAuthenticator =
- new CarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
- } else {
- mCarrierPrivilegeAuthenticator = null;
- }
+ mCarrierPrivilegeAuthenticator =
+ mDeps.makeCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
// To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -4157,11 +4165,11 @@
}
}
- private boolean hasCarrierPrivilegeForNetworkRequest(int callingUid,
- NetworkRequest networkRequest) {
+ private boolean hasCarrierPrivilegeForNetworkCaps(final int callingUid,
+ @NonNull final NetworkCapabilities caps) {
if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
- return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(callingUid,
- networkRequest);
+ return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ callingUid, caps);
}
return false;
}
@@ -4205,7 +4213,7 @@
}
}
if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
- if (!hasCarrierPrivilegeForNetworkRequest(nri.mUid, req)
+ if (!hasCarrierPrivilegeForNetworkCaps(nri.mUid, req.networkCapabilities)
&& !checkConnectivityRestrictedNetworksPermission(
nri.mPid, nri.mUid)) {
requestToBeReleased = req;
@@ -6140,13 +6148,6 @@
}
}
- private void ensureRequestableCapabilities(NetworkCapabilities networkCapabilities) {
- final String badCapability = networkCapabilities.describeFirstNonRequestableCapability();
- if (badCapability != null) {
- throw new IllegalArgumentException("Cannot request network with " + badCapability);
- }
- }
-
// This checks that the passed capabilities either do not request a
// specific SSID/SignalStrength, or the calling app has permission to do so.
private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
@@ -6204,7 +6205,7 @@
nai.onSignalStrengthThresholdsUpdated(thresholdsArray);
}
- private void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
+ private static void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
if (nc == null) {
return;
}
@@ -6217,7 +6218,7 @@
}
}
- private void ensureValid(NetworkCapabilities nc) {
+ private static void ensureListenableCapabilities(@NonNull final NetworkCapabilities nc) {
ensureValidNetworkSpecifier(nc);
if (nc.isPrivateDnsBroken()) {
throw new IllegalArgumentException("Can't request broken private DNS");
@@ -6227,6 +6228,14 @@
}
}
+ private void ensureRequestableCapabilities(@NonNull final NetworkCapabilities nc) {
+ ensureListenableCapabilities(nc);
+ final String badCapability = nc.describeFirstNonRequestableCapability();
+ if (badCapability != null) {
+ throw new IllegalArgumentException("Cannot request network with " + badCapability);
+ }
+ }
+
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@TargetApi(Build.VERSION_CODES.S)
private boolean isTargetSdkAtleast(int version, int callingUid,
@@ -6319,7 +6328,6 @@
if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
}
- ensureValid(networkCapabilities);
final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), reqType);
@@ -6461,7 +6469,6 @@
Binder.getCallingPid(), callingUid, callingPackageName);
restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
callingUid, callingPackageName);
- ensureValid(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -6528,7 +6535,7 @@
// There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
// can't request networks.
restrictBackgroundRequestForCaller(nc);
- ensureValid(nc);
+ ensureListenableCapabilities(nc);
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
@@ -6550,7 +6557,7 @@
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
- ensureValid(networkCapabilities);
+ ensureListenableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
@@ -7495,7 +7502,8 @@
nc.setOwnerUid(nai.networkCapabilities.getOwnerUid());
}
nai.declaredCapabilities = new NetworkCapabilities(nc);
- NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid);
+ NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
+ mCarrierPrivilegeAuthenticator);
}
/** Modifies |newNc| based on the capabilities of |underlyingNetworks| and |agentCaps|. */
@@ -10843,7 +10851,7 @@
} else {
mBpfNetMaps.removeNiceApp(uid);
}
- } catch (ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@@ -10858,7 +10866,7 @@
} else {
mBpfNetMaps.removeNaughtyApp(uid);
}
- } catch (ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@@ -10870,7 +10878,7 @@
try {
mBpfNetMaps.setUidRule(chain, uid,
allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
- } catch (ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@@ -10881,7 +10889,7 @@
try {
mBpfNetMaps.setChildChain(chain, enable);
- } catch (ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@@ -10908,7 +10916,7 @@
throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
+ chain);
}
- } catch (ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@@ -10918,7 +10926,7 @@
enforceNetworkStackOrSettingsPermission();
try {
mBpfNetMaps.swapActiveStatsMap();
- } catch (ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index c5107ad..ce955fd 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
@@ -25,7 +26,7 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.net.NetworkRequest;
+import android.net.NetworkCapabilities;
import android.net.NetworkSpecifier;
import android.net.TelephonyNetworkSpecifier;
import android.os.Build;
@@ -235,24 +236,30 @@
}
/**
- * Check if network request is allowed based upon carrrier service package.
+ * Check if a UID is the carrier service app of the subscription ID in the provided capabilities
*
- * Network request for {@link NET_CAPABILITY_CBS} is allowed if the caller has
- * carrier privilege and provides the carrier config. This function checks if caller
- * has the same and returns true if it has else false.
+ * This returns whether the passed UID is the carrier service package for the subscription ID
+ * stored in the telephony network specifier in the passed network capabilities.
+ * If the capabilities don't code for a cellular network, or if they don't have the
+ * subscription ID in their specifier, this returns false.
*
- * @param callingUid user identifier that uniquely identifies the caller.
- * @param networkRequest the network request for which the carrier privilege is checked.
- * @return true if caller has carrier privilege and provides the carrier config else false.
+ * This method can be used to check that a network request for {@link NET_CAPABILITY_CBS} is
+ * allowed for the UID of a caller, which must hold carrier privilege and provide the carrier
+ * config.
+ * It can also be used to check that a factory is entitled to grant access to a given network
+ * to a given UID on grounds that it is the carrier service package.
+ *
+ * @param callingUid uid of the app claimed to be the carrier service package.
+ * @param networkCapabilities the network capabilities for which carrier privilege is checked.
+ * @return true if uid provides the relevant carrier config else false.
*/
- public boolean hasCarrierPrivilegeForNetworkRequest(int callingUid,
- NetworkRequest networkRequest) {
- if (callingUid != Process.INVALID_UID) {
- final int subId = getSubIdFromNetworkSpecifier(
- networkRequest.getNetworkSpecifier());
- return callingUid == getCarrierServiceUidForSubId(subId);
- }
- return false;
+ public boolean hasCarrierPrivilegeForNetworkCapabilities(int callingUid,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ if (callingUid == Process.INVALID_UID) return false;
+ if (!networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)) return false;
+ final int subId = getSubIdFromNetworkSpecifier(networkCapabilities.getNetworkSpecifier());
+ if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) return false;
+ return callingUid == getCarrierServiceUidForSubId(subId);
}
@VisibleForTesting
@@ -286,7 +293,7 @@
if (specifier instanceof TelephonyNetworkSpecifier) {
return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
}
- return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
@VisibleForTesting
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index b9e2a5f..e917b3f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -60,6 +60,7 @@
import android.util.SparseArray;
import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.build.SdkLevel;
import com.android.server.ConnectivityService;
import java.io.PrintWriter;
@@ -1196,21 +1197,26 @@
*
* @param nc the capabilities to sanitize
* @param creatorUid the UID of the process creating this network agent
+ * @param authenticator the carrier privilege authenticator to check for telephony constraints
*/
public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
- final int creatorUid) {
+ final int creatorUid, @NonNull final CarrierPrivilegeAuthenticator authenticator) {
if (nc.hasTransport(TRANSPORT_TEST)) {
nc.restrictCapabilitiesForTestNetwork(creatorUid);
}
- if (!areAccessUidsAcceptableFromNetworkAgent(nc)) {
+ if (!areAccessUidsAcceptableFromNetworkAgent(nc, authenticator)) {
nc.setAccessUids(new ArraySet<>());
}
}
private static boolean areAccessUidsAcceptableFromNetworkAgent(
- @NonNull final NetworkCapabilities nc) {
+ @NonNull final NetworkCapabilities nc,
+ @Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
// NCs without access UIDs are fine.
if (!nc.hasAccessUids()) return true;
+ // S and below must never accept access UIDs, even if an agent sends them, because netd
+ // didn't support the required feature in S.
+ if (!SdkLevel.isAtLeastT()) return false;
// On a non-restricted network, access UIDs make no sense
if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;
@@ -1219,7 +1225,18 @@
// access UIDs
if (nc.hasTransport(TRANSPORT_TEST)) return true;
- // TODO : accept more supported cases
+ // Factories that make cell networks can allow the UID for the carrier service package.
+ // This can only work in T where there is support for CarrierPrivilegeAuthenticator
+ if (null != carrierPrivilegeAuthenticator
+ && nc.hasSingleTransport(TRANSPORT_CELLULAR)
+ && (1 == nc.getAccessUidsNoCopy().size())
+ && (carrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ nc.getAccessUidsNoCopy().valueAt(0), nc))) {
+ return true;
+ }
+
+ // TODO : accept Railway callers
+
return false;
}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index c9c1776..ac46054 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -58,7 +58,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -812,12 +811,8 @@
} else {
mBpfNetMaps.removeUidInterfaceRules(toIntArray(uids));
}
- } catch (ServiceSpecificException e) {
- // Silently ignore exception when device does not support eBPF, otherwise just log
- // the exception and do not crash
- if (e.errorCode != OsConstants.EOPNOTSUPP) {
- loge("Exception when updating permissions: ", e);
- }
+ } catch (RemoteException | ServiceSpecificException e) {
+ loge("Exception when updating permissions: ", e);
}
}
@@ -901,7 +896,7 @@
mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED,
toIntArray(uninstalledAppIds));
}
- } catch (ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Pass appId list of special permission failed." + e);
}
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt
new file mode 100644
index 0000000..049372f
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts
+
+import android.net.nsd.NsdManager
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.networkstack.apishim.ConnectivityFrameworkInitShimImpl
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
+
+private val cfiShim = ConnectivityFrameworkInitShimImpl.newInstance()
+
+@RunWith(DevSdkIgnoreRunner::class)
+// ConnectivityFrameworkInitializerTiramisu was added in T
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+class ConnectivityFrameworkInitializerTiramisuTest {
+ @Test
+ fun testServicesRegistered() {
+ val ctx = InstrumentationRegistry.getInstrumentation().context as android.content.Context
+ assertNotNull(ctx.getSystemService(NsdManager::class.java),
+ "NsdManager not registered")
+ }
+
+ // registerServiceWrappers can only be called during initialization and should throw otherwise
+ @Test(expected = IllegalStateException::class)
+ fun testThrows() {
+ cfiShim.registerServiceWrappers()
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 1f39fee..886b078 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -146,8 +146,9 @@
fun tearDown() {
agentsToCleanUp.forEach { it.unregister() }
callbacksToCleanUp.forEach { cm.unregisterNetworkCallback(it) }
+
+ // reader.stop() cleans up tun fd
reader.handler.post { reader.stop() }
- Os.close(iface.fileDescriptor.fileDescriptor)
handlerThread.quitSafely()
}
@@ -519,4 +520,4 @@
private fun <T> Context.assertHasService(manager: Class<T>): T {
return getSystemService(manager) ?: fail("Service $manager not found")
-}
\ No newline at end of file
+}
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
new file mode 100644
index 0000000..147fca9
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.cts;
+
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL;
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_NO;
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_YES;
+import static android.app.usage.NetworkStats.Bucket.METERED_ALL;
+import static android.app.usage.NetworkStats.Bucket.METERED_NO;
+import static android.app.usage.NetworkStats.Bucket.METERED_YES;
+import static android.app.usage.NetworkStats.Bucket.STATE_ALL;
+import static android.app.usage.NetworkStats.Bucket.STATE_DEFAULT;
+import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
+import static android.app.usage.NetworkStats.Bucket.TAG_NONE;
+import static android.app.usage.NetworkStats.Bucket.UID_ALL;
+
+import android.app.AppOpsManager;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.TrafficStats;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.telephony.TelephonyManager;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+
+public class NetworkStatsManagerTest extends InstrumentationTestCase {
+ private static final String LOG_TAG = "NetworkStatsManagerTest";
+ private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
+ private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
+
+ private static final long MINUTE = 1000 * 60;
+ private static final int TIMEOUT_MILLIS = 15000;
+
+ private static final String CHECK_CONNECTIVITY_URL = "http://www.265.com/";
+ private static final int HOST_RESOLUTION_RETRIES = 4;
+ private static final int HOST_RESOLUTION_INTERVAL_MS = 500;
+
+ private static final int NETWORK_TAG = 0xf00d;
+ private static final long THRESHOLD_BYTES = 2 * 1024 * 1024; // 2 MB
+
+ private abstract class NetworkInterfaceToTest {
+ private boolean mMetered;
+ private boolean mIsDefault;
+
+ abstract int getNetworkType();
+ abstract int getTransportType();
+
+ public boolean getMetered() {
+ return mMetered;
+ }
+
+ public void setMetered(boolean metered) {
+ this.mMetered = metered;
+ }
+
+ public boolean getIsDefault() {
+ return mIsDefault;
+ }
+
+ public void setIsDefault(boolean isDefault) {
+ mIsDefault = isDefault;
+ }
+
+ abstract String getSystemFeature();
+ abstract String getErrorMessage();
+ }
+
+ private final NetworkInterfaceToTest[] mNetworkInterfacesToTest =
+ new NetworkInterfaceToTest[] {
+ new NetworkInterfaceToTest() {
+ @Override
+ public int getNetworkType() {
+ return ConnectivityManager.TYPE_WIFI;
+ }
+
+ @Override
+ public int getTransportType() {
+ return NetworkCapabilities.TRANSPORT_WIFI;
+ }
+
+ @Override
+ public String getSystemFeature() {
+ return PackageManager.FEATURE_WIFI;
+ }
+
+ @Override
+ public String getErrorMessage() {
+ return " Please make sure you are connected to a WiFi access point.";
+ }
+ },
+ new NetworkInterfaceToTest() {
+ @Override
+ public int getNetworkType() {
+ return ConnectivityManager.TYPE_MOBILE;
+ }
+
+ @Override
+ public int getTransportType() {
+ return NetworkCapabilities.TRANSPORT_CELLULAR;
+ }
+
+ @Override
+ public String getSystemFeature() {
+ return PackageManager.FEATURE_TELEPHONY;
+ }
+
+ @Override
+ public String getErrorMessage() {
+ return " Please make sure you have added a SIM card with data plan to"
+ + " your phone, have enabled data over cellular and in case of"
+ + " dual SIM devices, have selected the right SIM "
+ + "for data connection.";
+ }
+ }
+ };
+
+ private String mPkg;
+ private NetworkStatsManager mNsm;
+ private ConnectivityManager mCm;
+ private PackageManager mPm;
+ private long mStartTime;
+ private long mEndTime;
+
+ private long mBytesRead;
+ private String mWriteSettingsMode;
+ private String mUsageStatsMode;
+
+ private void exerciseRemoteHost(Network network, URL url) throws Exception {
+ NetworkInfo networkInfo = mCm.getNetworkInfo(network);
+ if (networkInfo == null) {
+ Log.w(LOG_TAG, "Network info is null");
+ } else {
+ Log.w(LOG_TAG, "Network: " + networkInfo.toString());
+ }
+ InputStreamReader in = null;
+ HttpURLConnection urlc = null;
+ String originalKeepAlive = System.getProperty("http.keepAlive");
+ System.setProperty("http.keepAlive", "false");
+ try {
+ TrafficStats.setThreadStatsTag(NETWORK_TAG);
+ urlc = (HttpURLConnection) network.openConnection(url);
+ urlc.setConnectTimeout(TIMEOUT_MILLIS);
+ urlc.setUseCaches(false);
+ // Disable compression so we generate enough traffic that assertWithinPercentage will
+ // not be affected by the small amount of traffic (5-10kB) sent by the test harness.
+ urlc.setRequestProperty("Accept-Encoding", "identity");
+ urlc.connect();
+ boolean ping = urlc.getResponseCode() == 200;
+ if (ping) {
+ in = new InputStreamReader(
+ (InputStream) urlc.getContent());
+
+ mBytesRead = 0;
+ while (in.read() != -1) ++mBytesRead;
+ }
+ } catch (Exception e) {
+ Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
+ } finally {
+ TrafficStats.clearThreadStatsTag();
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // don't care
+ }
+ }
+ if (urlc != null) {
+ urlc.disconnect();
+ }
+ if (originalKeepAlive == null) {
+ System.clearProperty("http.keepAlive");
+ } else {
+ System.setProperty("http.keepAlive", originalKeepAlive);
+ }
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mNsm = (NetworkStatsManager) getInstrumentation().getContext()
+ .getSystemService(Context.NETWORK_STATS_SERVICE);
+ mNsm.setPollForce(true);
+
+ mCm = (ConnectivityManager) getInstrumentation().getContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ mPm = getInstrumentation().getContext().getPackageManager();
+
+ mPkg = getInstrumentation().getContext().getPackageName();
+
+ mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
+ setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
+ mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mWriteSettingsMode != null) {
+ setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
+ }
+ if (mUsageStatsMode != null) {
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
+ }
+ super.tearDown();
+ }
+
+ private void setAppOpsMode(String appop, String mode) throws Exception {
+ final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
+ SystemUtil.runShellCommand(command);
+ }
+
+ private String getAppOpsMode(String appop) throws Exception {
+ final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
+ String result = SystemUtil.runShellCommand(command);
+ if (result == null) {
+ Log.w(LOG_TAG, "App op " + appop + " could not be read.");
+ }
+ return result;
+ }
+
+ private boolean isInForeground() throws IOException {
+ String result = SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd activity get-uid-state " + Process.myUid());
+ return result.contains("FOREGROUND");
+ }
+
+ private class NetworkCallback extends ConnectivityManager.NetworkCallback {
+ private long mTolerance;
+ private URL mUrl;
+ public boolean success;
+ public boolean metered;
+ public boolean isDefault;
+
+ NetworkCallback(long tolerance, URL url) {
+ mTolerance = tolerance;
+ mUrl = url;
+ success = false;
+ metered = false;
+ isDefault = false;
+ }
+
+ // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4,
+ // we need to wait until IPv4 is available or the test will spuriously fail.
+ private void waitForHostResolution(Network network) {
+ for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) {
+ try {
+ network.getAllByName(mUrl.getHost());
+ return;
+ } catch (UnknownHostException e) {
+ SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS);
+ }
+ }
+ fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)",
+ mUrl.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS));
+ }
+
+ @Override
+ public void onAvailable(Network network) {
+ try {
+ mStartTime = System.currentTimeMillis() - mTolerance;
+ isDefault = network.equals(mCm.getActiveNetwork());
+ waitForHostResolution(network);
+ exerciseRemoteHost(network, mUrl);
+ mEndTime = System.currentTimeMillis() + mTolerance;
+ success = true;
+ metered = !mCm.getNetworkCapabilities(network)
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ synchronized (NetworkStatsManagerTest.this) {
+ NetworkStatsManagerTest.this.notify();
+ }
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "exercising remote host failed.", e);
+ success = false;
+ }
+ }
+ }
+
+ private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)
+ throws Exception {
+ boolean hasFeature = mPm.hasSystemFeature(
+ mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
+ if (!hasFeature) {
+ return false;
+ }
+ NetworkCallback callback = new NetworkCallback(tolerance, new URL(CHECK_CONNECTIVITY_URL));
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addTransportType(mNetworkInterfacesToTest[networkTypeIndex].getTransportType())
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build(), callback);
+ synchronized (this) {
+ try {
+ wait((int) (TIMEOUT_MILLIS * 1.2));
+ } catch (InterruptedException e) {
+ }
+ }
+ if (callback.success) {
+ mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered);
+ mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault);
+ return true;
+ }
+
+ // This will always fail at this point as we know 'hasFeature' is true.
+ assertFalse(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature()
+ + " is a reported system feature, "
+ + "however no corresponding connected network interface was found or the attempt "
+ + "to connect has timed out (timeout = " + TIMEOUT_MILLIS + "ms)."
+ + mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
+ return false;
+ }
+
+ private String getSubscriberId(int networkIndex) {
+ int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
+ if (ConnectivityManager.TYPE_MOBILE == networkType) {
+ TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
+ (telephonyManager) -> telephonyManager.getSubscriberId());
+ }
+ return "";
+ }
+
+ @AppModeFull
+ public void testDeviceSummary() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats.Bucket bucket = null;
+ try {
+ bucket = mNsm.querySummaryForDevice(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ } catch (RemoteException | SecurityException e) {
+ fail("testDeviceSummary fails with exception: " + e.toString());
+ }
+ assertNotNull(bucket);
+ assertTimestamps(bucket);
+ assertEquals(bucket.getState(), STATE_ALL);
+ assertEquals(bucket.getUid(), UID_ALL);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ bucket = mNsm.querySummaryForDevice(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ fail("negative testDeviceSummary fails: no exception thrown.");
+ } catch (RemoteException e) {
+ fail("testDeviceSummary fails with exception: " + e.toString());
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testUserSummary() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats.Bucket bucket = null;
+ try {
+ bucket = mNsm.querySummaryForUser(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ } catch (RemoteException | SecurityException e) {
+ fail("testUserSummary fails with exception: " + e.toString());
+ }
+ assertNotNull(bucket);
+ assertTimestamps(bucket);
+ assertEquals(bucket.getState(), STATE_ALL);
+ assertEquals(bucket.getUid(), UID_ALL);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ bucket = mNsm.querySummaryForUser(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ fail("negative testUserSummary fails: no exception thrown.");
+ } catch (RemoteException e) {
+ fail("testUserSummary fails with exception: " + e.toString());
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testAppSummary() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Use tolerance value that large enough to make sure stats of at
+ // least one bucket is included. However, this is possible that
+ // the test will see data of different app but with the same UID
+ // that created before testing.
+ // TODO: Consider query stats before testing and use the difference to verify.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ result = mNsm.querySummary(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ assertNotNull(result);
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ long totalTxPackets = 0;
+ long totalRxPackets = 0;
+ long totalTxBytes = 0;
+ long totalRxBytes = 0;
+ boolean hasCorrectMetering = false;
+ boolean hasCorrectDefaultStatus = false;
+ int expectedMetering = mNetworkInterfacesToTest[i].getMetered()
+ ? METERED_YES : METERED_NO;
+ int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault()
+ ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ assertTimestamps(bucket);
+ hasCorrectMetering |= bucket.getMetered() == expectedMetering;
+ if (bucket.getUid() == Process.myUid()) {
+ totalTxPackets += bucket.getTxPackets();
+ totalRxPackets += bucket.getRxPackets();
+ totalTxBytes += bucket.getTxBytes();
+ totalRxBytes += bucket.getRxBytes();
+ hasCorrectDefaultStatus |=
+ bucket.getDefaultNetworkStatus() == expectedDefaultStatus;
+ }
+ }
+ assertFalse(result.getNextBucket(bucket));
+ assertTrue("Incorrect metering for NetworkType: "
+ + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering);
+ assertTrue("Incorrect isDefault for NetworkType: "
+ + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus);
+ assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
+ assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
+ assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
+ assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.querySummary(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ fail("negative testAppSummary fails: no exception thrown.");
+ } catch (RemoteException e) {
+ fail("testAppSummary fails with exception: " + e.toString());
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testAppDetails() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ result = mNsm.queryDetails(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ long totalBytesWithSubscriberId = getTotalAndAssertNotEmpty(result);
+
+ // Test without filtering by subscriberId
+ result = mNsm.queryDetails(
+ mNetworkInterfacesToTest[i].getNetworkType(), null,
+ mStartTime, mEndTime);
+
+ assertTrue("More bytes with subscriberId filter than without.",
+ getTotalAndAssertNotEmpty(result) >= totalBytesWithSubscriberId);
+ } catch (RemoteException | SecurityException e) {
+ fail("testAppDetails fails with exception: " + e.toString());
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.queryDetails(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ fail("negative testAppDetails fails: no exception thrown.");
+ } catch (RemoteException e) {
+ fail("testAppDetails fails with exception: " + e.toString());
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testUidDetails() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ result = mNsm.queryDetailsForUid(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid());
+ assertNotNull(result);
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ long totalTxPackets = 0;
+ long totalRxPackets = 0;
+ long totalTxBytes = 0;
+ long totalRxBytes = 0;
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ assertTimestamps(bucket);
+ assertEquals(bucket.getState(), STATE_ALL);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ assertEquals(bucket.getUid(), Process.myUid());
+ totalTxPackets += bucket.getTxPackets();
+ totalRxPackets += bucket.getRxPackets();
+ totalTxBytes += bucket.getTxBytes();
+ totalRxBytes += bucket.getRxBytes();
+ }
+ assertFalse(result.getNextBucket(bucket));
+ assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
+ assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
+ assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
+ assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.queryDetailsForUid(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid());
+ fail("negative testUidDetails fails: no exception thrown.");
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testTagDetails() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ result = mNsm.queryDetailsForUidTag(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+ assertNotNull(result);
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ long totalTxPackets = 0;
+ long totalRxPackets = 0;
+ long totalTxBytes = 0;
+ long totalRxBytes = 0;
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ assertTimestamps(bucket);
+ assertEquals(bucket.getState(), STATE_ALL);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ assertEquals(bucket.getUid(), Process.myUid());
+ if (bucket.getTag() == NETWORK_TAG) {
+ totalTxPackets += bucket.getTxPackets();
+ totalRxPackets += bucket.getRxPackets();
+ totalTxBytes += bucket.getTxBytes();
+ totalRxBytes += bucket.getRxBytes();
+ }
+ }
+ assertTrue("No Rx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+ + " for uid " + Process.myUid(), totalRxBytes > 0);
+ assertTrue("No Rx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+ + " for uid " + Process.myUid(), totalRxPackets > 0);
+ assertTrue("No Tx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+ + " for uid " + Process.myUid(), totalTxBytes > 0);
+ assertTrue("No Tx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+ + " for uid " + Process.myUid(), totalTxPackets > 0);
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.queryDetailsForUidTag(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+ fail("negative testUidDetails fails: no exception thrown.");
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ class QueryResult {
+ public final int tag;
+ public final int state;
+ public final long total;
+
+ QueryResult(int tag, int state, NetworkStats stats) {
+ this.tag = tag;
+ this.state = state;
+ total = getTotalAndAssertNotEmpty(stats, tag, state);
+ }
+
+ public String toString() {
+ return String.format("QueryResult(tag=%s state=%s total=%d)",
+ tagToString(tag), stateToString(state), total);
+ }
+ }
+
+ private NetworkStats getNetworkStatsForTagState(int i, int tag, int state) {
+ return mNsm.queryDetailsForUidTagState(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid(), tag, state);
+ }
+
+ private void assertWithinPercentage(String msg, long expected, long actual, int percentage) {
+ long lowerBound = expected * (100 - percentage) / 100;
+ long upperBound = expected * (100 + percentage) / 100;
+ msg = String.format("%s: %d not within %d%% of %d", msg, actual, percentage, expected);
+ assertTrue(msg, lowerBound <= actual);
+ assertTrue(msg, upperBound >= actual);
+ }
+
+ private void assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag,
+ int expectedState, long maxUnexpected) {
+ long total = 0;
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ total += bucket.getRxBytes() + bucket.getTxBytes();
+ }
+ if (total <= maxUnexpected) return;
+
+ fail(String.format("More than %d bytes of traffic when querying for "
+ + "tag %s state %s. Last bucket: uid=%d tag=%s state=%s bytes=%d/%d",
+ maxUnexpected, tagToString(expectedTag), stateToString(expectedState),
+ bucket.getUid(), tagToString(bucket.getTag()), stateToString(bucket.getState()),
+ bucket.getRxBytes(), bucket.getTxBytes()));
+ }
+
+ @AppModeFull
+ public void testUidTagStateDetails() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ int currentState = isInForeground() ? STATE_FOREGROUND : STATE_DEFAULT;
+ int otherState = (currentState == STATE_DEFAULT) ? STATE_FOREGROUND : STATE_DEFAULT;
+
+ int[] tagsWithTraffic = {NETWORK_TAG, TAG_NONE};
+ int[] statesWithTraffic = {currentState, STATE_ALL};
+ ArrayList<QueryResult> resultsWithTraffic = new ArrayList<>();
+
+ int[] statesWithNoTraffic = {otherState};
+ int[] tagsWithNoTraffic = {NETWORK_TAG + 1};
+ ArrayList<QueryResult> resultsWithNoTraffic = new ArrayList<>();
+
+ // Expect to see traffic when querying for any combination of a tag in
+ // tagsWithTraffic and a state in statesWithTraffic.
+ for (int tag : tagsWithTraffic) {
+ for (int state : statesWithTraffic) {
+ result = getNetworkStatsForTagState(i, tag, state);
+ resultsWithTraffic.add(new QueryResult(tag, state, result));
+ result.close();
+ result = null;
+ }
+ }
+
+ // Expect that the results are within a few percentage points of each other.
+ // This is ensures that FIN retransmits after the transfer is complete don't cause
+ // the test to be flaky. The test URL currently returns just over 100k so this
+ // should not be too noisy. It also ensures that the traffic sent by the test
+ // harness, which is untagged, won't cause a failure.
+ long firstTotal = resultsWithTraffic.get(0).total;
+ for (QueryResult queryResult : resultsWithTraffic) {
+ assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 10);
+ }
+
+ // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
+ // state in statesWithNoTraffic.
+ for (int tag : tagsWithNoTraffic) {
+ for (int state : statesWithTraffic) {
+ result = getNetworkStatsForTagState(i, tag, state);
+ assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
+ result.close();
+ result = null;
+ }
+ }
+ for (int tag : tagsWithTraffic) {
+ for (int state : statesWithNoTraffic) {
+ result = getNetworkStatsForTagState(i, tag, state);
+ assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
+ result.close();
+ result = null;
+ }
+ }
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.queryDetailsForUidTag(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+ fail("negative testUidDetails fails: no exception thrown.");
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testCallback() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+
+ TestUsageCallback usageCallback = new TestUsageCallback();
+ HandlerThread thread = new HandlerThread("callback-thread");
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
+ getSubscriberId(i), THRESHOLD_BYTES, usageCallback, handler);
+
+ // TODO: Force traffic and check whether the callback is invoked.
+ // Right now the test only covers whether the callback can be registered, but not
+ // whether it is invoked upon data usage since we don't have a scalable way of
+ // storing files of >2MB in CTS.
+
+ mNsm.unregisterUsageCallback(usageCallback);
+ }
+ }
+
+ private String tagToString(Integer tag) {
+ if (tag == null) return "null";
+ switch (tag) {
+ case TAG_NONE:
+ return "TAG_NONE";
+ default:
+ return "0x" + Integer.toHexString(tag);
+ }
+ }
+
+ private String stateToString(Integer state) {
+ if (state == null) return "null";
+ switch (state) {
+ case STATE_ALL:
+ return "STATE_ALL";
+ case STATE_DEFAULT:
+ return "STATE_DEFAULT";
+ case STATE_FOREGROUND:
+ return "STATE_FOREGROUND";
+ }
+ throw new IllegalArgumentException("Unknown state " + state);
+ }
+
+ private long getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag,
+ Integer expectedState) {
+ assertTrue(result != null);
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ long totalTxPackets = 0;
+ long totalRxPackets = 0;
+ long totalTxBytes = 0;
+ long totalRxBytes = 0;
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ assertTimestamps(bucket);
+ if (expectedTag != null) assertEquals(bucket.getTag(), (int) expectedTag);
+ if (expectedState != null) assertEquals(bucket.getState(), (int) expectedState);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ if (bucket.getUid() == Process.myUid()) {
+ totalTxPackets += bucket.getTxPackets();
+ totalRxPackets += bucket.getRxPackets();
+ totalTxBytes += bucket.getTxBytes();
+ totalRxBytes += bucket.getRxBytes();
+ }
+ }
+ assertFalse(result.getNextBucket(bucket));
+ String msg = String.format("uid %d tag %s state %s",
+ Process.myUid(), tagToString(expectedTag), stateToString(expectedState));
+ assertTrue("No Rx bytes usage for " + msg, totalRxBytes > 0);
+ assertTrue("No Rx packets usage for " + msg, totalRxPackets > 0);
+ assertTrue("No Tx bytes usage for " + msg, totalTxBytes > 0);
+ assertTrue("No Tx packets usage for " + msg, totalTxPackets > 0);
+
+ return totalRxBytes + totalTxBytes;
+ }
+
+ private long getTotalAndAssertNotEmpty(NetworkStats result) {
+ return getTotalAndAssertNotEmpty(result, null, STATE_ALL);
+ }
+
+ private void assertTimestamps(final NetworkStats.Bucket bucket) {
+ assertTrue("Start timestamp " + bucket.getStartTimeStamp() + " is less than "
+ + mStartTime, bucket.getStartTimeStamp() >= mStartTime);
+ assertTrue("End timestamp " + bucket.getEndTimeStamp() + " is greater than "
+ + mEndTime, bucket.getEndTimeStamp() <= mEndTime);
+ }
+
+ private static class TestUsageCallback extends NetworkStatsManager.UsageCallback {
+ @Override
+ public void onThresholdReached(int networkType, String subscriberId) {
+ Log.v(LOG_TAG, "Called onThresholdReached for networkType=" + networkType
+ + " subscriberId=" + subscriberId);
+ }
+ }
+}
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 4d4e7b9..530fa91 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -53,7 +53,8 @@
// android_library does not include JNI libs: include NetworkStack dependencies here
"libnativehelper_compat_libc++",
"libnetworkstackutilsjni",
- "libcom_android_connectivity_com_android_net_module_util_jni",
+ "libandroid_net_connectivity_com_android_net_module_util_jni",
+ "libservice-connectivity",
],
jarjar_rules: ":connectivity-jarjar-rules",
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 2985c41..0132525 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -264,6 +264,7 @@
import android.net.RouteInfo;
import android.net.RouteInfoParcel;
import android.net.SocketKeepalive;
+import android.net.TelephonyNetworkSpecifier;
import android.net.TransportInfo;
import android.net.UidRange;
import android.net.UidRangeParcel;
@@ -334,6 +335,7 @@
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
import com.android.server.ConnectivityService.NetworkRequestInfo;
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
@@ -528,6 +530,8 @@
@Mock SystemConfigManager mSystemConfigManager;
@Mock Resources mResources;
@Mock PacProxyManager mPacProxyManager;
+ @Mock BpfNetMaps mBpfNetMaps;
+ @Mock CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
// BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
// underlying binder calls.
@@ -969,8 +973,6 @@
* @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
*/
public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
- assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET));
-
ConnectivityManager.NetworkCallback callback = null;
final ConditionVariable validatedCv = new ConditionVariable();
if (validated) {
@@ -1858,6 +1860,12 @@
}
@Override
+ public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
+ @NonNull final Context context, @NonNull final TelephonyManager tm) {
+ return SdkLevel.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
+ }
+
+ @Override
public boolean intentFilterEquals(final PendingIntent a, final PendingIntent b) {
return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b));
}
@@ -1950,6 +1958,11 @@
return super.isFeatureEnabled(context, name, defaultEnabled);
}
}
+
+ @Override
+ public BpfNetMaps getBpfNetMaps(INetd netd) {
+ return mBpfNetMaps;
+ }
}
private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) {
@@ -10126,7 +10139,7 @@
// A connected VPN should have interface rules set up. There are two expected invocations,
// one during the VPN initial connection, one during the VPN LinkProperties update.
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
- verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
@@ -10135,7 +10148,7 @@
waitForIdle();
// Disconnected VPN should have interface rules removed
- verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
}
@@ -10152,7 +10165,7 @@
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
// Legacy VPN should not have interface rules set up
- verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
}
@Test
@@ -10168,7 +10181,7 @@
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
// IPv6 unreachable route should not be misinterpreted as a default route
- verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
}
@Test
@@ -10185,33 +10198,33 @@
// Connected VPN should have interface rules set up. There are two expected invocations,
// one during VPN uid update, one during VPN LinkProperties update
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
- verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
- reset(mMockNetd);
- InOrder inOrder = inOrder(mMockNetd);
+ reset(mBpfNetMaps);
+ InOrder inOrder = inOrder(mBpfNetMaps);
lp.setInterfaceName("tun1");
mMockVpn.sendLinkProperties(lp);
waitForIdle();
// VPN handover (switch to a new interface) should result in rules being updated (old rules
// removed first, then new rules added)
- inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ inOrder.verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ inOrder.verify(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- reset(mMockNetd);
+ reset(mBpfNetMaps);
lp = new LinkProperties();
lp.setInterfaceName("tun1");
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
mMockVpn.sendLinkProperties(lp);
waitForIdle();
// VPN not routing everything should no longer have interface filtering rules
- verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- reset(mMockNetd);
+ reset(mBpfNetMaps);
lp = new LinkProperties();
lp.setInterfaceName("tun1");
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
@@ -10219,7 +10232,7 @@
mMockVpn.sendLinkProperties(lp);
waitForIdle();
// Back to routing all IPv6 traffic should have filtering rules
- verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ verify(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
}
@@ -10248,8 +10261,8 @@
mMockVpn.establish(lp, VPN_UID, vpnRanges);
assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
- reset(mMockNetd);
- InOrder inOrder = inOrder(mMockNetd);
+ reset(mBpfNetMaps);
+ InOrder inOrder = inOrder(mBpfNetMaps);
// Update to new range which is old range minus APP1, i.e. only APP2
final Set<UidRange> newRanges = new HashSet<>(asList(
@@ -10260,9 +10273,9 @@
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
// Verify old rules are removed before new rules are added
- inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ inOrder.verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ inOrder.verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP2_UID);
}
@@ -14637,43 +14650,157 @@
agent.getNetwork().getNetId(),
intToUidRangeStableParcels(uids),
preferenceOrder);
- inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
+ if (SdkLevel.isAtLeastT()) {
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
+ }
uids.add(300);
uids.add(400);
nc.setAccessUids(uids);
agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
- cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ } else {
+ cb.assertNoCallback();
+ }
uids.remove(200);
final NativeUidRangeConfig uids300400Parcel = new NativeUidRangeConfig(
agent.getNetwork().getNetId(),
intToUidRangeStableParcels(uids),
preferenceOrder);
- inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
+ if (SdkLevel.isAtLeastT()) {
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
+ }
nc.setAccessUids(uids);
agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
- cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
- inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
+ } else {
+ cb.assertNoCallback();
+ }
uids.clear();
uids.add(600);
nc.setAccessUids(uids);
agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
- cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ } else {
+ cb.assertNoCallback();
+ }
final NativeUidRangeConfig uids600Parcel = new NativeUidRangeConfig(
agent.getNetwork().getNetId(),
intToUidRangeStableParcels(uids),
preferenceOrder);
- inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
- inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
+ if (SdkLevel.isAtLeastT()) {
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
+ }
uids.clear();
nc.setAccessUids(uids);
agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
- cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().isEmpty());
- inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().isEmpty());
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
+ } else {
+ cb.assertNoCallback();
+ verify(mMockNetd, never()).networkAddUidRangesParcel(any());
+ verify(mMockNetd, never()).networkRemoveUidRangesParcel(any());
+ }
+
+ }
+
+ @Test
+ public void testCbsAccessUids() throws Exception {
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+
+ // In this test TEST_PACKAGE_UID will be the UID of the carrier service UID.
+ doReturn(true).when(mCarrierPrivilegeAuthenticator)
+ .hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
+
+ final ArraySet<Integer> serviceUidSet = new ArraySet<>();
+ serviceUidSet.add(TEST_PACKAGE_UID);
+ final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
+ nonServiceUidSet.add(TEST_PACKAGE_UID2);
+ final ArraySet<Integer> serviceUidSetPlus = new ArraySet<>();
+ serviceUidSetPlus.add(TEST_PACKAGE_UID);
+ serviceUidSetPlus.add(TEST_PACKAGE_UID2);
+
+ final TestNetworkCallback cb = new TestNetworkCallback();
+
+ // Simulate a restricted telephony network. The telephony factory is entitled to set
+ // the access UID to the service package on any of its restricted networks.
+ final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
+
+ // Cell gets to set the service UID as access UID
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build(), cb);
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+ new LinkProperties(), ncb.build());
+ mCellNetworkAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ ncb.setAccessUids(serviceUidSet);
+ mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(mCellNetworkAgent,
+ caps -> caps.getAccessUids().equals(serviceUidSet));
+ } else {
+ // S must ignore access UIDs.
+ cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+ }
+
+ // ...but not to some other UID. Rejection sets UIDs to the empty set
+ ncb.setAccessUids(nonServiceUidSet);
+ mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(mCellNetworkAgent,
+ caps -> caps.getAccessUids().isEmpty());
+ } else {
+ // S must ignore access UIDs.
+ cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+ }
+
+ // ...and also not to multiple UIDs even including the service UID
+ ncb.setAccessUids(serviceUidSetPlus);
+ mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+
+ mCellNetworkAgent.disconnect();
+ cb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ mCm.unregisterNetworkCallback(cb);
+
+ // Must be unset before touching the transports, because remove and add transport types
+ // check the specifier on the builder immediately, contradicting normal builder semantics
+ // TODO : fix the builder
+ ncb.setNetworkSpecifier(null);
+ ncb.removeTransportType(TRANSPORT_CELLULAR);
+ ncb.addTransportType(TRANSPORT_WIFI);
+ // Wifi does not get to set access UID, even to the correct UID
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build(), cb);
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
+ new LinkProperties(), ncb.build());
+ mWiFiNetworkAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+ ncb.setAccessUids(serviceUidSet);
+ mWiFiNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+ mCm.unregisterNetworkCallback(cb);
}
/**
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
index d193aa9..157507b 100644
--- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@@ -40,7 +41,9 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.TelephonyNetworkSpecifier;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import com.android.networkstack.apishim.TelephonyManagerShimImpl;
@@ -66,27 +69,25 @@
@RunWith(DevSdkIgnoreRunner.class)
@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public class CarrierPrivilegeAuthenticatorTest {
- private static final String PACKAGE_NAME =
- CarrierPrivilegeAuthenticatorTest.class.getPackage().getName();
- private static final int TEST_SIM_SLOT_INDEX = 0;
- private static final int TEST_SUBSCRIPTION_ID_1 = 2;
- private static final int TEST_SUBSCRIPTION_ID_2 = 3;
+ private static final int SUBSCRIPTION_COUNT = 2;
+ private static final int TEST_SUBSCRIPTION_ID = 1;
@NonNull private final Context mContext;
@NonNull private final TelephonyManager mTelephonyManager;
@NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim;
@NonNull private final PackageManager mPackageManager;
- @NonNull private CarrierPrivilegeAuthenticatorChild mCarrierPrivilegeAuthenticator;
+ @NonNull private TestCarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
private final int mCarrierConfigPkgUid = 12345;
private final String mTestPkg = "com.android.server.connectivity.test";
- public class CarrierPrivilegeAuthenticatorChild extends CarrierPrivilegeAuthenticator {
- CarrierPrivilegeAuthenticatorChild(@NonNull final Context c,
+ public class TestCarrierPrivilegeAuthenticator extends CarrierPrivilegeAuthenticator {
+ TestCarrierPrivilegeAuthenticator(@NonNull final Context c,
@NonNull final TelephonyManager t) {
super(c, t, mTelephonyManagerShim);
}
@Override
protected int getSlotIndex(int subId) {
+ if (SubscriptionManager.DEFAULT_SUBSCRIPTION_ID == subId) return TEST_SUBSCRIPTION_ID;
return subId;
}
}
@@ -100,7 +101,7 @@
@Before
public void setUp() throws Exception {
- doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(SUBSCRIPTION_COUNT).when(mTelephonyManager).getActiveModemCount();
doReturn(mTestPkg).when(mTelephonyManagerShim)
.getCarrierServicePackageNameForLogicalSlot(anyInt());
doReturn(mPackageManager).when(mContext).getPackageManager();
@@ -109,7 +110,7 @@
doReturn(applicationInfo).when(mPackageManager)
.getApplicationInfo(eq(mTestPkg), anyInt());
mCarrierPrivilegeAuthenticator =
- new CarrierPrivilegeAuthenticatorChild(mContext, mTelephonyManager);
+ new TestCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
}
private IntentFilter getIntentFilter() {
@@ -126,7 +127,6 @@
verify(mTelephonyManagerShim, atLeastOnce())
.addCarrierPrivilegesListener(anyInt(), any(), captor.capture());
} catch (UnsupportedApiLevelException e) {
-
}
return captor.getAllValues();
}
@@ -160,10 +160,10 @@
networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
- assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
- mCarrierConfigPkgUid, networkRequestBuilder.build()));
- assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
- mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
}
@Test
@@ -192,10 +192,10 @@
final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
- assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
- mCarrierConfigPkgUid, networkRequestBuilder.build()));
- assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
- mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
}
@Test
@@ -215,9 +215,30 @@
.getApplicationInfo(eq(mTestPkg), anyInt());
listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {});
- assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
- mCarrierConfigPkgUid, networkRequestBuilder.build()));
- assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
- mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
+ }
+
+ @Test
+ public void testDefaultSubscription() throws Exception {
+ final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+ networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+
+ networkRequestBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+
+ // The builder for NetworkRequest doesn't allow removing the transport as long as a
+ // specifier is set, so unset it first. TODO : fix the builder
+ networkRequestBuilder.setNetworkSpecifier((NetworkSpecifier) null);
+ networkRequestBuilder.removeTransportType(TRANSPORT_CELLULAR);
+ networkRequestBuilder.addTransportType(TRANSPORT_WIFI);
+ networkRequestBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
}
}