Merge "Ability for DPM to specify fallback mechanism"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 06d4416..6996ad9 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -28,6 +28,10 @@
"postsubmit": [
{
"name": "TetheringPrivilegedTests"
+ },
+ // TODO: move to presubmit when known green.
+ {
+ "name": "bpf_existence_test"
}
],
"mainline-presubmit": [
@@ -54,6 +58,10 @@
},
{
"name": "TetheringCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ },
+ // TODO: move to mainline-presubmit when known green.
+ {
+ "name": "bpf_existence_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
}
],
"imports": [
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index b8dfd3d..ebc04d6 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -25,6 +25,8 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
method public void systemReady();
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateMeteredNetworkAllowList(int, boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateMeteredNetworkDenyList(int, boolean);
field public static final String ACTION_CLEAR_DNS_CACHE = "android.net.action.CLEAR_DNS_CACHE";
field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION";
field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY";
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 9735252..38ceabb 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -5564,4 +5564,48 @@
public static Range<Integer> getIpSecNetIdRange() {
return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1);
}
+
+ /**
+ * Allow target application using metered network.
+ *
+ * @param uid uid of target app
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void updateMeteredNetworkAllowList(final int uid, final boolean add) {
+ try {
+ mService.updateMeteredNetworkAllowList(uid, add);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (IllegalStateException ie) {
+ throw ie;
+ }
+ }
+
+ /**
+ * Disallow target application using metered network.
+ *
+ * @param uid uid of target app
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void updateMeteredNetworkDenyList(final int uid, final boolean add) {
+ try {
+ mService.updateMeteredNetworkDenyList(uid, add);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (IllegalStateException ie) {
+ throw ie;
+ }
+ }
}
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 7c39bec..5740d85 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -230,4 +230,8 @@
void unofferNetwork(in INetworkOfferCallback callback);
void setTestAllowBadWifiUntil(long timeMs);
+
+ void updateMeteredNetworkAllowList(int uid, boolean add);
+
+ void updateMeteredNetworkDenyList(int uid, boolean add);
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a906537..3280f18 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -10584,4 +10584,34 @@
return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
}
}
+
+ @Override
+ public void updateMeteredNetworkAllowList(final int uid, final boolean add) {
+ enforceNetworkStackOrSettingsPermission();
+
+ try {
+ if (add) {
+ mNetd.bandwidthAddNiceApp(uid);
+ } else {
+ mNetd.bandwidthRemoveNiceApp(uid);
+ }
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void updateMeteredNetworkDenyList(final int uid, final boolean add) {
+ enforceNetworkStackOrSettingsPermission();
+
+ try {
+ if (add) {
+ mNetd.bandwidthAddNaughtyApp(uid);
+ } else {
+ mNetd.bandwidthRemoveNaughtyApp(uid);
+ }
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 708d600..916b566 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -893,7 +893,7 @@
@Test
public void testDefault() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
if (!SdkLevel.isAtLeastS() && (
SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
|| SystemProperties.getInt("service.adb.tcp.port", -1) > -1)) {
@@ -986,7 +986,7 @@
@Test
public void testAppAllowed() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
@@ -1007,7 +1007,7 @@
@Test
public void testAppDisallowed() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
FileDescriptor localFd = openSocketFd(TEST_HOST, 80, TIMEOUT_MS);
FileDescriptor remoteFd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
@@ -1041,8 +1041,8 @@
@Test
public void testExcludedRoutes() throws Exception {
- if (!supportedHardware()) return;
- if (!SdkLevel.isAtLeastT()) return;
+ assumeTrue(supportedHardware());
+ assumeTrue(SdkLevel.isAtLeastT());
// Shell app must not be put in here or it would kill the ADB-over-network use case
String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
@@ -1062,7 +1062,7 @@
@Test
public void testIncludedRoutes() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
// Shell app must not be put in here or it would kill the ADB-over-network use case
String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
@@ -1081,8 +1081,8 @@
@Test
public void testInterleavedRoutes() throws Exception {
- if (!supportedHardware()) return;
- if (!SdkLevel.isAtLeastT()) return;
+ assumeTrue(supportedHardware());
+ assumeTrue(SdkLevel.isAtLeastT());
// Shell app must not be put in here or it would kill the ADB-over-network use case
String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
@@ -1109,7 +1109,7 @@
@Test
public void testGetConnectionOwnerUidSecurity() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
DatagramSocket s;
InetAddress address = InetAddress.getByName("localhost");
@@ -1131,7 +1131,7 @@
@Test
public void testSetProxy() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
ProxyInfo initialProxy = mCM.getDefaultProxy();
// Receiver for the proxy change broadcast.
BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
@@ -1171,7 +1171,7 @@
@Test
public void testSetProxyDisallowedApps() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
ProxyInfo initialProxy = mCM.getDefaultProxy();
String disallowedApps = mPackageName;
@@ -1197,7 +1197,7 @@
@Test
public void testNoProxy() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
ProxyInfo initialProxy = mCM.getDefaultProxy();
BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
proxyBroadcastReceiver.register();
@@ -1232,7 +1232,7 @@
@Test
public void testBindToNetworkWithProxy() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
String allowedApps = mPackageName;
Network initialNetwork = mCM.getActiveNetwork();
ProxyInfo initialProxy = mCM.getDefaultProxy();
@@ -1474,7 +1474,7 @@
}
private void maybeExpectVpnTransportInfo(Network network) {
- if (!SdkLevel.isAtLeastS()) return;
+ assumeTrue(SdkLevel.isAtLeastS());
final NetworkCapabilities vpnNc = mCM.getNetworkCapabilities(network);
assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
final TransportInfo ti = vpnNc.getTransportInfo();
@@ -1526,7 +1526,7 @@
*/
@Test
public void testDownloadWithDownloadManagerDisallowed() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
// Start a VPN with DownloadManager package in disallowed list.
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
new file mode 100644
index 0000000..a56f76e
--- /dev/null
+++ b/tests/mts/Android.bp
@@ -0,0 +1,36 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "bpf_existence_test",
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
+ require_root: true,
+ static_libs: [
+ "libbase",
+ "libmodules-utils-build",
+ ],
+ srcs: [
+ "bpf_existence_test.cpp",
+ ],
+ compile_multilib: "first",
+ min_sdk_version: "29", // Ensure test runs on Q and above.
+}
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
new file mode 100644
index 0000000..5e3df57
--- /dev/null
+++ b/tests/mts/bpf_existence_test.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright 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.
+ *
+ * bpf_existence_test.cpp - checks that the device runs expected BPF programs
+ */
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <android-base/strings.h>
+#include <android-base/properties.h>
+#include <android-modules-utils/sdk_level.h>
+
+#include <gtest/gtest.h>
+
+using std::find;
+using std::string;
+using std::vector;
+
+using android::modules::sdklevel::IsAtLeastR;
+using android::modules::sdklevel::IsAtLeastS;
+using android::modules::sdklevel::IsAtLeastT;
+
+class BpfExistenceTest : public ::testing::Test {
+};
+
+static const vector<string> INTRODUCED_R = {
+ "/sys/fs/bpf/prog_offload_schedcls_ingress_tether_ether",
+ "/sys/fs/bpf/prog_offload_schedcls_ingress_tether_rawip",
+};
+
+static const vector<string> INTRODUCED_S = {
+ "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_downstream4_ether",
+ "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_downstream4_rawip",
+ "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_downstream6_ether",
+ "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_downstream6_rawip",
+ "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_upstream4_ether",
+ "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_upstream4_rawip",
+ "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_upstream6_ether",
+ "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_upstream6_rawip",
+};
+
+static const vector<string> REMOVED_S = {
+ "/sys/fs/bpf/prog_offload_schedcls_ingress_tether_ether",
+ "/sys/fs/bpf/prog_offload_schedcls_ingress_tether_rawip",
+};
+
+static const vector<string> INTRODUCED_T = {
+};
+
+static const vector<string> REMOVED_T = {
+};
+
+void addAll(vector<string>* a, const vector<string>& b) {
+ a->insert(a->end(), b.begin(), b.end());
+}
+
+void removeAll(vector<string>* a, const vector<string> b) {
+ for (const auto& toRemove : b) {
+ auto iter = find(a->begin(), a->end(), toRemove);
+ while (iter != a->end()) {
+ a->erase(iter);
+ iter = find(a->begin(), a->end(), toRemove);
+ }
+ }
+}
+
+void getFileLists(vector<string>* expected, vector<string>* unexpected) {
+ unexpected->clear();
+ expected->clear();
+
+ addAll(unexpected, INTRODUCED_R);
+ addAll(unexpected, INTRODUCED_S);
+ addAll(unexpected, INTRODUCED_T);
+
+ if (IsAtLeastR()) {
+ addAll(expected, INTRODUCED_R);
+ removeAll(unexpected, INTRODUCED_R);
+ // Nothing removed in R.
+ }
+
+ if (IsAtLeastS()) {
+ addAll(expected, INTRODUCED_S);
+ removeAll(expected, REMOVED_S);
+
+ addAll(unexpected, REMOVED_S);
+ removeAll(unexpected, INTRODUCED_S);
+ }
+
+ // Nothing added or removed in SCv2.
+
+ if (IsAtLeastT()) {
+ addAll(expected, INTRODUCED_T);
+ removeAll(expected, REMOVED_T);
+
+ addAll(unexpected, REMOVED_T);
+ removeAll(unexpected, INTRODUCED_T);
+ }
+}
+
+void checkFiles() {
+ vector<string> mustExist;
+ vector<string> mustNotExist;
+
+ getFileLists(&mustExist, &mustNotExist);
+
+ for (const auto& file : mustExist) {
+ EXPECT_EQ(0, access(file.c_str(), R_OK)) << file << " does not exist";
+ }
+ for (const auto& file : mustNotExist) {
+ int ret = access(file.c_str(), R_OK);
+ int err = errno;
+ EXPECT_EQ(-1, ret) << file << " unexpectedly exists";
+ if (ret == -1) {
+ EXPECT_EQ(ENOENT, err) << " accessing " << file << " failed with errno " << err;
+ }
+ }
+}
+
+TEST_F(BpfExistenceTest, TestPrograms) {
+ // Pre-flight check to ensure test has been updated.
+ uint64_t buildVersionSdk = android::base::GetUintProperty<uint64_t>("ro.build.version.sdk", 0);
+ ASSERT_NE(0, buildVersionSdk) << "Unable to determine device SDK version";
+ if (buildVersionSdk > 33 && buildVersionSdk != 10000) {
+ FAIL() << "Unknown OS version " << buildVersionSdk << ", please update this test";
+ }
+
+ // Only unconfined root is guaranteed to be able to access everything in /sys/fs/bpf.
+ ASSERT_EQ(0, getuid()) << "This test must run as root.";
+
+ checkFiles();
+}
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index 08a3007..6e51069 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -220,6 +220,26 @@
TEST_SUBSCRIBER_ID));
}
+ @Test
+ public void testQueryTaggedSummary() throws Exception {
+ final long startTime = 1;
+ final long endTime = 100;
+
+ reset(mStatsSession);
+ when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
+ when(mStatsSession.getTaggedSummaryForAllUid(any(NetworkTemplate.class),
+ anyLong(), anyLong()))
+ .thenReturn(new android.net.NetworkStats(0, 0));
+ final NetworkTemplate template = new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
+ .setMeteredness(NetworkStats.Bucket.METERED_YES).build();
+ NetworkStats stats = mManager.queryTaggedSummary(template, startTime, endTime);
+
+ verify(mStatsSession, times(1)).getTaggedSummaryForAllUid(
+ eq(template), eq(startTime), eq(endTime));
+
+ assertFalse(stats.hasNextBucket());
+ }
+
private void assertBucketMatches(Entry expected, NetworkStats.Bucket actual) {
assertEquals(expected.uid, actual.getUid());
assertEquals(expected.rxBytes, actual.getRxBytes());