Merge "add post_notification permission to manifest"
diff --git a/OWNERS b/OWNERS
index 62c5737..07a775e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,4 @@
set noparent
file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+
+per-file **IpSec* = file:platform/frameworks/base:master:/services/core/java/com/android/server/vcn/OWNERS
\ No newline at end of file
diff --git a/OWNERS_core_networking b/OWNERS_core_networking
index 6847c74..bc1d002 100644
--- a/OWNERS_core_networking
+++ b/OWNERS_core_networking
@@ -12,6 +12,7 @@
maze@google.com
nuccachen@google.com
paulhu@google.com
+prohr@google.com
reminv@google.com
satk@google.com
waynema@google.com
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
new file mode 100644
index 0000000..a6627fe
--- /dev/null
+++ b/OWNERS_core_networking_xts
@@ -0,0 +1,2 @@
+lorenzo@google.com
+satk@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 90312a4..780ba26 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -23,11 +23,31 @@
},
{
"name": "TetheringIntegrationTests"
+ },
+ {
+ "name": "traffic_controller_unit_test"
+ },
+ {
+ "name": "libnetworkstats_test"
}
],
"postsubmit": [
{
"name": "TetheringPrivilegedTests"
+ },
+ // TODO: move to presubmit when known green.
+ {
+ "name": "bpf_existence_test"
+ },
+ {
+ "name": "libclat_test"
+ },
+ {
+ "name": "traffic_controller_unit_test",
+ "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
+ },
+ {
+ "name": "libnetworkstats_test"
}
],
"mainline-presubmit": [
@@ -44,6 +64,12 @@
},
{
"name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ },
+ {
+ "name": "traffic_controller_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ },
+ {
+ "name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
}
],
"mainline-postsubmit": [
@@ -54,6 +80,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": [
@@ -61,6 +91,9 @@
"path": "frameworks/base/core/java/android/net"
},
{
+ "path": "frameworks/opt/net/ethernet"
+ },
+ {
"path": "packages/modules/NetworkStack"
},
{
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index bf28a14..c0e6749 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -21,6 +21,7 @@
java_defaults {
name: "TetheringApiLevel",
sdk_version: "module_current",
+ target_sdk_version: "31",
min_sdk_version: "30",
}
@@ -52,10 +53,12 @@
"framework-statsd.stubs.module_lib",
"framework-tethering.impl",
"framework-wifi",
+ "framework-bluetooth",
"unsupportedappusage",
],
plugins: ["java_api_finder"],
manifest: "AndroidManifestBase.xml",
+ lint: { strict_updatability_linting: true },
}
// build tethering static library, used to compile both variants of the tethering.
@@ -69,6 +72,7 @@
"NetworkStackApiCurrentShims",
],
apex_available: ["com.android.tethering"],
+ lint: { strict_updatability_linting: true },
}
android_library {
@@ -81,6 +85,7 @@
"NetworkStackApiStableShims",
],
apex_available: ["com.android.tethering"],
+ lint: { strict_updatability_linting: true },
}
// Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
@@ -106,6 +111,7 @@
static_libs: [
"libnet_utils_device_common_bpfjni",
"libnetjniutils",
+ "libtcutils",
],
// We cannot use plain "libc++" here to link libc++ dynamically because it results in:
@@ -147,6 +153,7 @@
optimize: {
proguard_flags_files: ["proguard.flags"],
},
+ lint: { strict_updatability_linting: true },
}
// Non-updatable tethering running in the system server process for devices not using the module
@@ -159,6 +166,7 @@
// InProcessTethering is a replacement for Tethering
overrides: ["Tethering"],
apex_available: ["com.android.tethering"],
+ lint: { strict_updatability_linting: true },
}
// Updatable tethering packaged for finalized API
@@ -175,6 +183,7 @@
"privapp_whitelist_com.android.networkstack.tethering",
],
apex_available: ["com.android.tethering"],
+ lint: { strict_updatability_linting: true },
}
android_app {
@@ -194,9 +203,11 @@
"privapp_whitelist_com.android.networkstack.tethering",
],
apex_available: ["com.android.tethering"],
+ lint: { strict_updatability_linting: true },
}
sdk {
name: "tethering-module-sdk",
bootclasspath_fragments: ["com.android.tethering-bootclasspath-fragment"],
+ systemserverclasspath_fragments: ["com.android.tethering-systemserverclasspath-fragment"],
}
diff --git a/Tethering/AndroidManifest.xml b/Tethering/AndroidManifest.xml
index 4b2a3ec..b832e16 100644
--- a/Tethering/AndroidManifest.xml
+++ b/Tethering/AndroidManifest.xml
@@ -19,14 +19,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.networkstack.tethering"
android:sharedUserId="android.uid.networkstack">
- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
<!-- Permissions must be defined here, and not in the base manifest, as the tethering
running in the system server process does not need any permission, and having
privileged permissions added would cause crashes on startup unless they are also
- added to the privileged permissions allowlist for that package. -->
+ added to the privileged permissions allowlist for that package. EntitlementManager
+ would set exact alarm but declare SCHEDULE_EXACT_ALARM is not necessary here because
+ privilege application would be in the allowlist. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 7863572..d5049ec 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -31,6 +31,7 @@
// package names and keys, so that apex will be unused anyway.
apps: ["TetheringNext"], // Replace to "Tethering" if ConnectivityNextEnableDefaults is false.
}
+enable_tethering_next_apex = true
// This is a placeholder comment to avoid merge conflicts
// as the above target may have different "enabled" values
// depending on the branch
@@ -44,18 +45,28 @@
bootclasspath_fragments: [
"com.android.tethering-bootclasspath-fragment",
],
- java_libs: [
- "service-connectivity",
+ systemserverclasspath_fragments: [
+ "com.android.tethering-systemserverclasspath-fragment",
],
multilib: {
first: {
- jni_libs: ["libservice-connectivity"],
+ jni_libs: [
+ "libservice-connectivity",
+ "libcom_android_connectivity_com_android_net_module_util_jni",
+ "libtraffic_controller_jni",
+ ],
},
both: {
jni_libs: ["libframework-connectivity-jni"],
},
},
+ binaries: [
+ "clatd",
+ ],
+ canned_fs_config: "canned_fs_config",
bpfs: [
+ "clatd.o_mainline",
+ "netd.o_mainline",
"offload.o",
"test.o",
],
@@ -88,6 +99,7 @@
name: "com.android.tethering-bootclasspath-fragment",
contents: [
"framework-connectivity",
+ "framework-connectivity-tiramisu",
"framework-tethering",
],
apex_available: ["com.android.tethering"],
@@ -109,15 +121,28 @@
// Additional hidden API flag files to override the defaults. This must only be
// modified by the Soong or platform compat team.
hidden_api: {
- max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ max_target_r_low_priority: [
+ "hiddenapi/hiddenapi-max-target-r-loprio.txt",
+ ],
+ max_target_o_low_priority: [
+ "hiddenapi/hiddenapi-max-target-o-low-priority.txt",
+ "hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt",
+ ],
unsupported: ["hiddenapi/hiddenapi-unsupported.txt"],
},
}
+systemserverclasspath_fragment {
+ name: "com.android.tethering-systemserverclasspath-fragment",
+ standalone_contents: ["service-connectivity"],
+ apex_available: ["com.android.tethering"],
+}
+
override_apex {
name: "com.android.tethering.inprocess",
base: "com.android.tethering",
package_name: "com.android.tethering.inprocess",
+ enabled: enable_tethering_next_apex,
apps: [
"ServiceConnectivityResources",
"InProcessTethering",
diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config
new file mode 100644
index 0000000..06e9617
--- /dev/null
+++ b/Tethering/apex/canned_fs_config
@@ -0,0 +1,2 @@
+/bin/for-system 0 1000 0550
+/bin/for-system/clatd 1029 1029 06755
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
new file mode 100644
index 0000000..88c77f2
--- /dev/null
+++ b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
@@ -0,0 +1,87 @@
+Landroid/net/nsd/DnsSdTxtRecord;-><init>()V
+Landroid/net/nsd/DnsSdTxtRecord;-><init>(Landroid/net/nsd/DnsSdTxtRecord;)V
+Landroid/net/nsd/DnsSdTxtRecord;-><init>([B)V
+Landroid/net/nsd/DnsSdTxtRecord;->contains(Ljava/lang/String;)Z
+Landroid/net/nsd/DnsSdTxtRecord;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/net/nsd/DnsSdTxtRecord;->get(Ljava/lang/String;)Ljava/lang/String;
+Landroid/net/nsd/DnsSdTxtRecord;->getKey(I)Ljava/lang/String;
+Landroid/net/nsd/DnsSdTxtRecord;->getRawData()[B
+Landroid/net/nsd/DnsSdTxtRecord;->getValue(I)[B
+Landroid/net/nsd/DnsSdTxtRecord;->getValue(Ljava/lang/String;)[B
+Landroid/net/nsd/DnsSdTxtRecord;->getValueAsString(I)Ljava/lang/String;
+Landroid/net/nsd/DnsSdTxtRecord;->insert([B[BI)V
+Landroid/net/nsd/DnsSdTxtRecord;->keyCount()I
+Landroid/net/nsd/DnsSdTxtRecord;->mData:[B
+Landroid/net/nsd/DnsSdTxtRecord;->mSeperator:B
+Landroid/net/nsd/DnsSdTxtRecord;->remove(Ljava/lang/String;)I
+Landroid/net/nsd/DnsSdTxtRecord;->set(Ljava/lang/String;Ljava/lang/String;)V
+Landroid/net/nsd/DnsSdTxtRecord;->size()I
+Landroid/net/nsd/INsdManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/net/nsd/INsdManager$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/net/nsd/INsdManager$Stub$Proxy;->getMessenger()Landroid/os/Messenger;
+Landroid/net/nsd/INsdManager$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/net/nsd/INsdManager$Stub$Proxy;->setEnabled(Z)V
+Landroid/net/nsd/INsdManager$Stub;-><init>()V
+Landroid/net/nsd/INsdManager$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/net/nsd/INsdManager$Stub;->TRANSACTION_getMessenger:I
+Landroid/net/nsd/INsdManager$Stub;->TRANSACTION_setEnabled:I
+Landroid/net/nsd/INsdManager;->setEnabled(Z)V
+Landroid/net/nsd/NsdManager;-><init>(Landroid/content/Context;Landroid/net/nsd/INsdManager;)V
+Landroid/net/nsd/NsdManager;->BASE:I
+Landroid/net/nsd/NsdManager;->checkListener(Ljava/lang/Object;)V
+Landroid/net/nsd/NsdManager;->checkProtocol(I)V
+Landroid/net/nsd/NsdManager;->checkServiceInfo(Landroid/net/nsd/NsdServiceInfo;)V
+Landroid/net/nsd/NsdManager;->DBG:Z
+Landroid/net/nsd/NsdManager;->DISABLE:I
+Landroid/net/nsd/NsdManager;->disconnect()V
+Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES:I
+Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES_FAILED:I
+Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES_STARTED:I
+Landroid/net/nsd/NsdManager;->ENABLE:I
+Landroid/net/nsd/NsdManager;->EVENT_NAMES:Landroid/util/SparseArray;
+Landroid/net/nsd/NsdManager;->fatal(Ljava/lang/String;)V
+Landroid/net/nsd/NsdManager;->FIRST_LISTENER_KEY:I
+Landroid/net/nsd/NsdManager;->getListenerKey(Ljava/lang/Object;)I
+Landroid/net/nsd/NsdManager;->getMessenger()Landroid/os/Messenger;
+Landroid/net/nsd/NsdManager;->getNsdServiceInfoType(Landroid/net/nsd/NsdServiceInfo;)Ljava/lang/String;
+Landroid/net/nsd/NsdManager;->init()V
+Landroid/net/nsd/NsdManager;->mAsyncChannel:Lcom/android/internal/util/AsyncChannel;
+Landroid/net/nsd/NsdManager;->mConnected:Ljava/util/concurrent/CountDownLatch;
+Landroid/net/nsd/NsdManager;->mContext:Landroid/content/Context;
+Landroid/net/nsd/NsdManager;->mHandler:Landroid/net/nsd/NsdManager$ServiceHandler;
+Landroid/net/nsd/NsdManager;->mListenerKey:I
+Landroid/net/nsd/NsdManager;->mListenerMap:Landroid/util/SparseArray;
+Landroid/net/nsd/NsdManager;->mMapLock:Ljava/lang/Object;
+Landroid/net/nsd/NsdManager;->mService:Landroid/net/nsd/INsdManager;
+Landroid/net/nsd/NsdManager;->mServiceMap:Landroid/util/SparseArray;
+Landroid/net/nsd/NsdManager;->nameOf(I)Ljava/lang/String;
+Landroid/net/nsd/NsdManager;->NATIVE_DAEMON_EVENT:I
+Landroid/net/nsd/NsdManager;->nextListenerKey()I
+Landroid/net/nsd/NsdManager;->putListener(Ljava/lang/Object;Landroid/net/nsd/NsdServiceInfo;)I
+Landroid/net/nsd/NsdManager;->REGISTER_SERVICE:I
+Landroid/net/nsd/NsdManager;->REGISTER_SERVICE_FAILED:I
+Landroid/net/nsd/NsdManager;->REGISTER_SERVICE_SUCCEEDED:I
+Landroid/net/nsd/NsdManager;->removeListener(I)V
+Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE:I
+Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE_FAILED:I
+Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE_SUCCEEDED:I
+Landroid/net/nsd/NsdManager;->SERVICE_FOUND:I
+Landroid/net/nsd/NsdManager;->SERVICE_LOST:I
+Landroid/net/nsd/NsdManager;->setEnabled(Z)V
+Landroid/net/nsd/NsdManager;->STOP_DISCOVERY:I
+Landroid/net/nsd/NsdManager;->STOP_DISCOVERY_FAILED:I
+Landroid/net/nsd/NsdManager;->STOP_DISCOVERY_SUCCEEDED:I
+Landroid/net/nsd/NsdManager;->TAG:Ljava/lang/String;
+Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE:I
+Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE_FAILED:I
+Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE_SUCCEEDED:I
+Landroid/net/nsd/NsdServiceInfo;-><init>(Ljava/lang/String;Ljava/lang/String;)V
+Landroid/net/nsd/NsdServiceInfo;->getTxtRecord()[B
+Landroid/net/nsd/NsdServiceInfo;->getTxtRecordSize()I
+Landroid/net/nsd/NsdServiceInfo;->mHost:Ljava/net/InetAddress;
+Landroid/net/nsd/NsdServiceInfo;->mPort:I
+Landroid/net/nsd/NsdServiceInfo;->mServiceName:Ljava/lang/String;
+Landroid/net/nsd/NsdServiceInfo;->mServiceType:Ljava/lang/String;
+Landroid/net/nsd/NsdServiceInfo;->mTxtRecord:Landroid/util/ArrayMap;
+Landroid/net/nsd/NsdServiceInfo;->setTxtRecords(Ljava/lang/String;)V
+Landroid/net/nsd/NsdServiceInfo;->TAG:Ljava/lang/String;
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt b/Tethering/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt
new file mode 100644
index 0000000..211b847
--- /dev/null
+++ b/Tethering/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt
@@ -0,0 +1 @@
+Landroid/net/nsd/INsdManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/nsd/INsdManager;
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 6e64570..c82a993 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -53,6 +53,7 @@
apex_available: ["com.android.tethering"],
permitted_packages: ["android.net"],
min_sdk_version: "30",
+ lint: { strict_updatability_linting: true },
}
filegroup {
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
deleted file mode 100644
index f9e4824..0000000
--- a/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <arpa/inet.h>
-#include <jni.h>
-#include <linux/if_arp.h>
-#include <linux/if_ether.h>
-#include <linux/netlink.h>
-#include <linux/pkt_cls.h>
-#include <linux/pkt_sched.h>
-#include <linux/rtnetlink.h>
-#include <nativehelper/JNIHelp.h>
-#include <net/if.h>
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/utsname.h>
-
-// TODO: use unique_fd.
-#define BPF_FD_JUST_USE_INT
-#include "BpfSyscallWrappers.h"
-#include "bpf_tethering.h"
-#include "nativehelper/scoped_utf_chars.h"
-
-// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
-#define CLS_BPF_NAME_LEN 256
-
-// Classifier name. See cls_bpf_ops in net/sched/cls_bpf.c.
-#define CLS_BPF_KIND_NAME "bpf"
-
-namespace android {
-// Sync from system/netd/server/NetlinkCommands.h
-const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
-const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
-
-static void throwIOException(JNIEnv *env, const char* msg, int error) {
- jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
-}
-
-// TODO: move to frameworks/libs/net/common/native for sharing with
-// system/netd/server/OffloadUtils.{c, h}.
-static void sendAndProcessNetlinkResponse(JNIEnv* env, const void* req, int len) {
- int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); // TODO: use unique_fd
- if (fd == -1) {
- throwIOException(env, "socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)", errno);
- return;
- }
-
- static constexpr int on = 1;
- if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
- throwIOException(env, "setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, 1)", errno);
- close(fd);
- return;
- }
-
- // this is needed to get valid strace netlink parsing, it allocates the pid
- if (bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR))) {
- throwIOException(env, "bind(fd, {AF_NETLINK, 0, 0})", errno);
- close(fd);
- return;
- }
-
- // we do not want to receive messages from anyone besides the kernel
- if (connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR))) {
- throwIOException(env, "connect(fd, {AF_NETLINK, 0, 0})", errno);
- close(fd);
- return;
- }
-
- int rv = send(fd, req, len, 0);
-
- if (rv == -1) {
- throwIOException(env, "send(fd, req, len, 0)", errno);
- close(fd);
- return;
- }
-
- if (rv != len) {
- throwIOException(env, "send(fd, req, len, 0)", EMSGSIZE);
- close(fd);
- return;
- }
-
- struct {
- nlmsghdr h;
- nlmsgerr e;
- char buf[256];
- } resp = {};
-
- rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
-
- if (rv == -1) {
- throwIOException(env, "recv() failed", errno);
- close(fd);
- return;
- }
-
- if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
- jniThrowExceptionFmt(env, "java/io/IOException", "recv() returned short packet: %d", rv);
- close(fd);
- return;
- }
-
- if (resp.h.nlmsg_len != (unsigned)rv) {
- jniThrowExceptionFmt(env, "java/io/IOException",
- "recv() returned invalid header length: %d != %d", resp.h.nlmsg_len,
- rv);
- close(fd);
- return;
- }
-
- if (resp.h.nlmsg_type != NLMSG_ERROR) {
- jniThrowExceptionFmt(env, "java/io/IOException",
- "recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
- close(fd);
- return;
- }
-
- if (resp.e.error) { // returns 0 on success
- throwIOException(env, "NLMSG_ERROR message return error", -resp.e.error);
- }
- close(fd);
- return;
-}
-
-static int hardwareAddressType(const char* interface) {
- int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
- if (fd < 0) return -errno;
-
- struct ifreq ifr = {};
- // We use strncpy() instead of strlcpy() since kernel has to be able
- // to handle non-zero terminated junk passed in by userspace anyway,
- // and this way too long interface names (more than IFNAMSIZ-1 = 15
- // characters plus terminating NULL) will not get truncated to 15
- // characters and zero-terminated and thus potentially erroneously
- // match a truncated interface if one were to exist.
- strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
-
- int rv;
- if (ioctl(fd, SIOCGIFHWADDR, &ifr, sizeof(ifr))) {
- rv = -errno;
- } else {
- rv = ifr.ifr_hwaddr.sa_family;
- }
-
- close(fd);
- return rv;
-}
-
-// -----------------------------------------------------------------------------
-// TODO - just use BpfUtils.h once that is available in sc-mainline-prod and has kernelVersion()
-//
-// In the mean time copying verbatim from:
-// system/bpf/libbpf_android/include/bpf/BpfUtils.h
-// and
-// system/bpf/libbpf_android/BpfUtils.cpp
-
-#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
-
-static unsigned kernelVersion() {
- struct utsname buf;
- int ret = uname(&buf);
- if (ret) return 0;
-
- unsigned kver_major;
- unsigned kver_minor;
- unsigned kver_sub;
- char discard;
- ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub, &discard);
- // Check the device kernel version
- if (ret < 3) return 0;
-
- return KVER(kver_major, kver_minor, kver_sub);
-}
-
-static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
- return kernelVersion() >= KVER(major, minor, sub);
-}
-// -----------------------------------------------------------------------------
-
-static jboolean com_android_networkstack_tethering_BpfUtils_isEthernet(JNIEnv* env, jobject clazz,
- jstring iface) {
- ScopedUtfChars interface(env, iface);
-
- int rv = hardwareAddressType(interface.c_str());
- if (rv < 0) {
- jniThrowExceptionFmt(env, "java/io/IOException",
- "Get hardware address type of interface %s failed: %s",
- interface.c_str(), strerror(-rv));
- return false;
- }
-
- // Backwards compatibility with pre-GKI kernels that use various custom
- // ARPHRD_* for their cellular interface
- switch (rv) {
- // ARPHRD_PUREIP on at least some Mediatek Android kernels
- // example: wembley with 4.19 kernel
- case 520:
- // in Linux 4.14+ rmnet support was upstreamed and ARHRD_RAWIP became 519,
- // but it is 530 on at least some Qualcomm Android 4.9 kernels with rmnet
- // example: Pixel 3 family
- case 530:
- // >5.4 kernels are GKI2.0 and thus upstream compatible, however 5.10
- // shipped with Android S, so (for safety) let's limit ourselves to
- // >5.10, ie. 5.11+ as a guarantee we're on Android T+ and thus no
- // longer need this non-upstream compatibility logic
- static bool is_pre_5_11_kernel = !isAtLeastKernelVersion(5, 11, 0);
- if (is_pre_5_11_kernel) return false;
- }
-
- switch (rv) {
- case ARPHRD_ETHER:
- return true;
- case ARPHRD_NONE:
- case ARPHRD_PPP:
- case ARPHRD_RAWIP:
- return false;
- default:
- jniThrowExceptionFmt(env, "java/io/IOException",
- "Unknown hardware address type %d on interface %s", rv,
- interface.c_str());
- return false;
- }
-}
-
-// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
-// direct-action
-static void com_android_networkstack_tethering_BpfUtils_tcFilterAddDevBpf(
- JNIEnv* env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio, jshort proto,
- jstring bpfProgPath) {
- ScopedUtfChars pathname(env, bpfProgPath);
-
- const int bpfFd = bpf::retrieveProgram(pathname.c_str());
- if (bpfFd == -1) {
- throwIOException(env, "retrieveProgram failed", errno);
- return;
- }
-
- struct {
- nlmsghdr n;
- tcmsg t;
- struct {
- nlattr attr;
- // The maximum classifier name length is defined in
- // tcf_proto_ops in include/net/sch_generic.h.
- char str[NLMSG_ALIGN(sizeof(CLS_BPF_KIND_NAME))];
- } kind;
- struct {
- nlattr attr;
- struct {
- nlattr attr;
- __u32 u32;
- } fd;
- struct {
- nlattr attr;
- char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)];
- } name;
- struct {
- nlattr attr;
- __u32 u32;
- } flags;
- } options;
- } req = {
- .n =
- {
- .nlmsg_len = sizeof(req),
- .nlmsg_type = RTM_NEWTFILTER,
- .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
- },
- .t =
- {
- .tcm_family = AF_UNSPEC,
- .tcm_ifindex = ifIndex,
- .tcm_handle = TC_H_UNSPEC,
- .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
- ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
- .tcm_info = static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
- htons(static_cast<uint16_t>(proto))),
- },
- .kind =
- {
- .attr =
- {
- .nla_len = sizeof(req.kind),
- .nla_type = TCA_KIND,
- },
- .str = CLS_BPF_KIND_NAME,
- },
- .options =
- {
- .attr =
- {
- .nla_len = sizeof(req.options),
- .nla_type = NLA_F_NESTED | TCA_OPTIONS,
- },
- .fd =
- {
- .attr =
- {
- .nla_len = sizeof(req.options.fd),
- .nla_type = TCA_BPF_FD,
- },
- .u32 = static_cast<__u32>(bpfFd),
- },
- .name =
- {
- .attr =
- {
- .nla_len = sizeof(req.options.name),
- .nla_type = TCA_BPF_NAME,
- },
- // Visible via 'tc filter show', but
- // is overwritten by strncpy below
- .str = "placeholder",
- },
- .flags =
- {
- .attr =
- {
- .nla_len = sizeof(req.options.flags),
- .nla_type = TCA_BPF_FLAGS,
- },
- .u32 = TCA_BPF_FLAG_ACT_DIRECT,
- },
- },
- };
-
- snprintf(req.options.name.str, sizeof(req.options.name.str), "%s:[*fsobj]",
- basename(pathname.c_str()));
-
- // The exception may be thrown from sendAndProcessNetlinkResponse. Close the file descriptor of
- // BPF program before returning the function in any case.
- sendAndProcessNetlinkResponse(env, &req, sizeof(req));
- close(bpfFd);
-}
-
-// tc filter del dev .. in/egress prio .. protocol ..
-static void com_android_networkstack_tethering_BpfUtils_tcFilterDelDev(JNIEnv* env, jobject clazz,
- jint ifIndex,
- jboolean ingress,
- jshort prio, jshort proto) {
- const struct {
- nlmsghdr n;
- tcmsg t;
- } req = {
- .n =
- {
- .nlmsg_len = sizeof(req),
- .nlmsg_type = RTM_DELTFILTER,
- .nlmsg_flags = NETLINK_REQUEST_FLAGS,
- },
- .t =
- {
- .tcm_family = AF_UNSPEC,
- .tcm_ifindex = ifIndex,
- .tcm_handle = TC_H_UNSPEC,
- .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
- ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
- .tcm_info = static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
- htons(static_cast<uint16_t>(proto))),
- },
- };
-
- sendAndProcessNetlinkResponse(env, &req, sizeof(req));
-}
-
-/*
- * JNI registration.
- */
-static const JNINativeMethod gMethods[] = {
- /* name, signature, funcPtr */
- {"isEthernet", "(Ljava/lang/String;)Z",
- (void*)com_android_networkstack_tethering_BpfUtils_isEthernet},
- {"tcFilterAddDevBpf", "(IZSSLjava/lang/String;)V",
- (void*)com_android_networkstack_tethering_BpfUtils_tcFilterAddDevBpf},
- {"tcFilterDelDev", "(IZSS)V",
- (void*)com_android_networkstack_tethering_BpfUtils_tcFilterDelDev},
-};
-
-int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/networkstack/tethering/BpfUtils", gMethods,
- NELEM(gMethods));
-}
-
-}; // namespace android
diff --git a/Tethering/jni/onload.cpp b/Tethering/jni/onload.cpp
index 72895f1..ed80128 100644
--- a/Tethering/jni/onload.cpp
+++ b/Tethering/jni/onload.cpp
@@ -23,6 +23,7 @@
namespace android {
int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
+int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name);
int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env);
int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env);
int register_com_android_networkstack_tethering_util_TetheringUtils(JNIEnv* env);
@@ -39,9 +40,10 @@
if (register_com_android_net_module_util_BpfMap(env,
"com/android/networkstack/tethering/util/BpfMap") < 0) return JNI_ERR;
- if (register_com_android_networkstack_tethering_BpfCoordinator(env) < 0) return JNI_ERR;
+ if (register_com_android_net_module_util_TcUtils(env,
+ "com/android/networkstack/tethering/util/TcUtils") < 0) return JNI_ERR;
- if (register_com_android_networkstack_tethering_BpfUtils(env) < 0) return JNI_ERR;
+ if (register_com_android_networkstack_tethering_BpfCoordinator(env) < 0) return JNI_ERR;
return JNI_VERSION_1_6;
}
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
index f62df7f..6735317 100644
--- a/Tethering/proguard.flags
+++ b/Tethering/proguard.flags
@@ -8,6 +8,10 @@
native <methods>;
}
+-keep class com.android.networkstack.tethering.util.TcUtils {
+ native <methods>;
+}
+
-keepclassmembers public class * extends com.android.networkstack.tethering.util.Struct {
*;
}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index b4228da..2bb19db 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -614,10 +614,8 @@
return false;
}
- if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
- // BT configures the interface elsewhere: only start DHCP.
- // TODO: make all tethering types behave the same way, and delete the bluetooth
- // code that calls into NetworkManagementService directly.
+ if (shouldNotConfigureBluetoothInterface()) {
+ // Interface was already configured elsewhere, only start DHCP.
return configureDhcp(enabled, mIpv4Address, null /* clientAddress */);
}
@@ -651,12 +649,15 @@
return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
}
+ private boolean shouldNotConfigureBluetoothInterface() {
+ // Before T, bluetooth tethering configures the interface elsewhere.
+ return (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
+ }
+
private LinkAddress requestIpv4Address(final boolean useLastAddress) {
if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
- if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
- return new LinkAddress(BLUETOOTH_IFACE_ADDR);
- }
+ if (shouldNotConfigureBluetoothInterface()) return new LinkAddress(BLUETOOTH_IFACE_ADDR);
return mPrivateAddressCoordinator.requestDownstreamAddress(this, useLastAddress);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfUtils.java b/Tethering/src/com/android/networkstack/tethering/BpfUtils.java
index 4f095cf..77efb51 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfUtils.java
@@ -24,6 +24,8 @@
import androidx.annotation.NonNull;
+import com.android.net.module.util.TcUtils;
+
import java.io.IOException;
/**
@@ -53,11 +55,11 @@
static final boolean DOWNSTREAM = true;
static final boolean UPSTREAM = false;
- // The priority of clat/tether hooks - smaller is higher priority.
+ // The priority of tether hooks - smaller is higher priority.
// TC tether is higher priority then TC clat to match XDP winning over TC.
- // Sync from system/netd/server/OffloadUtils.h.
- static final short PRIO_TETHER6 = 1;
- static final short PRIO_TETHER4 = 2;
+ // Sync from system/netd/server/TcUtils.h.
+ static final short PRIO_TETHER6 = 2;
+ static final short PRIO_TETHER4 = 3;
// note that the above must be lower than PRIO_CLAT from netd's OffloadUtils.cpp
private static String makeProgPath(boolean downstream, int ipVersion, boolean ether) {
@@ -82,7 +84,7 @@
boolean ether;
try {
- ether = isEthernet(iface);
+ ether = TcUtils.isEthernet(iface);
} catch (IOException e) {
throw new IOException("isEthernet(" + params.index + "[" + iface + "]) failure: " + e);
}
@@ -90,7 +92,7 @@
try {
// tc filter add dev .. ingress prio 1 protocol ipv6 bpf object-pinned /sys/fs/bpf/...
// direct-action
- tcFilterAddDevBpf(params.index, INGRESS, PRIO_TETHER6, (short) ETH_P_IPV6,
+ TcUtils.tcFilterAddDevBpf(params.index, INGRESS, PRIO_TETHER6, (short) ETH_P_IPV6,
makeProgPath(downstream, 6, ether));
} catch (IOException e) {
throw new IOException("tc filter add dev (" + params.index + "[" + iface
@@ -100,7 +102,7 @@
try {
// tc filter add dev .. ingress prio 2 protocol ip bpf object-pinned /sys/fs/bpf/...
// direct-action
- tcFilterAddDevBpf(params.index, INGRESS, PRIO_TETHER4, (short) ETH_P_IP,
+ TcUtils.tcFilterAddDevBpf(params.index, INGRESS, PRIO_TETHER4, (short) ETH_P_IP,
makeProgPath(downstream, 4, ether));
} catch (IOException e) {
throw new IOException("tc filter add dev (" + params.index + "[" + iface
@@ -121,7 +123,7 @@
try {
// tc filter del dev .. ingress prio 1 protocol ipv6
- tcFilterDelDev(params.index, INGRESS, PRIO_TETHER6, (short) ETH_P_IPV6);
+ TcUtils.tcFilterDelDev(params.index, INGRESS, PRIO_TETHER6, (short) ETH_P_IPV6);
} catch (IOException e) {
throw new IOException("tc filter del dev (" + params.index + "[" + iface
+ "]) ingress prio PRIO_TETHER6 protocol ipv6 failure: " + e);
@@ -129,18 +131,10 @@
try {
// tc filter del dev .. ingress prio 2 protocol ip
- tcFilterDelDev(params.index, INGRESS, PRIO_TETHER4, (short) ETH_P_IP);
+ TcUtils.tcFilterDelDev(params.index, INGRESS, PRIO_TETHER4, (short) ETH_P_IP);
} catch (IOException e) {
throw new IOException("tc filter del dev (" + params.index + "[" + iface
+ "]) ingress prio PRIO_TETHER4 protocol ip failure: " + e);
}
}
-
- private static native boolean isEthernet(String iface) throws IOException;
-
- private static native void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio,
- short proto, String bpfProgPath) throws IOException;
-
- private static native void tcFilterDelDev(int ifIndex, boolean ingress, short prio,
- short proto) throws IOException;
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 78f2afc..db9a64f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -123,6 +123,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -133,7 +134,12 @@
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.networkstack.apishim.common.BluetoothPanShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.TetheringUtils;
@@ -264,9 +270,15 @@
private int mOffloadStatus = TETHER_HARDWARE_OFFLOAD_STOPPED;
private EthernetManager.TetheredInterfaceRequest mEthernetIfaceRequest;
+ private TetheredInterfaceRequestShim mBluetoothIfaceRequest;
private String mConfiguredEthernetIface;
+ private String mConfiguredBluetoothIface;
private EthernetCallback mEthernetCallback;
+ private TetheredInterfaceCallbackShim mBluetoothCallback;
private SettingsObserver mSettingsObserver;
+ private BluetoothPan mBluetoothPan;
+ private PanServiceListener mBluetoothPanListener;
+ private ArrayList<Pair<Boolean, IIntResultListener>> mPendingPanRequests;
public Tethering(TetheringDependencies deps) {
mLog.mark("Tethering.constructed");
@@ -276,6 +288,11 @@
mLooper = mDeps.getTetheringLooper();
mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper);
+ // This is intended to ensrure that if something calls startTethering(bluetooth) just after
+ // bluetooth is enabled. Before onServiceConnected is called, store the calls into this
+ // list and handle them as soon as onServiceConnected is called.
+ mPendingPanRequests = new ArrayList<>();
+
mTetherStates = new ArrayMap<>();
mConnectedClientsTracker = new ConnectedClientsTracker();
@@ -524,14 +541,16 @@
}
}
- // This method needs to exist because TETHERING_BLUETOOTH and TETHERING_WIGIG can't use
- // enableIpServing.
+ // This method needs to exist because TETHERING_BLUETOOTH before Android T and TETHERING_WIGIG
+ // can't use enableIpServing.
private void processInterfaceStateChange(final String iface, boolean enabled) {
// Do not listen to USB interface state changes or USB interface add/removes. USB tethering
// is driven only by USB_ACTION broadcasts.
final int type = ifaceNameToType(iface);
if (type == TETHERING_USB || type == TETHERING_NCM) return;
+ if (type == TETHERING_BLUETOOTH && SdkLevel.isAtLeastT()) return;
+
if (enabled) {
ensureIpServerStarted(iface);
} else {
@@ -701,35 +720,151 @@
return;
}
- adapter.getProfileProxy(mContext, new ServiceListener() {
- @Override
- public void onServiceDisconnected(int profile) { }
+ if (mBluetoothPanListener != null && mBluetoothPanListener.isConnected()) {
+ // The PAN service is connected. Enable or disable bluetooth tethering.
+ // When bluetooth tethering is enabled, any time a PAN client pairs with this
+ // host, bluetooth will bring up a bt-pan interface and notify tethering to
+ // enable IP serving.
+ setBluetoothTetheringSettings(mBluetoothPan, enable, listener);
+ return;
+ }
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- // Clear identify is fine because caller already pass tethering permission at
- // ConnectivityService#startTethering()(or stopTethering) before the control comes
- // here. Bluetooth will check tethering permission again that there is
- // Context#getOpPackageName() under BluetoothPan#setBluetoothTethering() to get
- // caller's package name for permission check.
- // Calling BluetoothPan#setBluetoothTethering() here means the package name always
- // be system server. If calling identity is not cleared, that package's uid might
- // not match calling uid and end up in permission denied.
- final long identityToken = Binder.clearCallingIdentity();
- try {
- ((BluetoothPan) proxy).setBluetoothTethering(enable);
- } finally {
- Binder.restoreCallingIdentity(identityToken);
+ // The reference of IIntResultListener should only exist when application want to start
+ // tethering but tethering is not bound to pan service yet. Even if the calling process
+ // dies, the referenice of IIntResultListener would still keep in mPendingPanRequests. Once
+ // tethering bound to pan service (onServiceConnected) or bluetooth just crash
+ // (onServiceDisconnected), all the references from mPendingPanRequests would be cleared.
+ mPendingPanRequests.add(new Pair(enable, listener));
+
+ // Bluetooth tethering is not a popular feature. To avoid bind to bluetooth pan service all
+ // the time but user never use bluetooth tethering. mBluetoothPanListener is created first
+ // time someone calls a bluetooth tethering method (even if it's just to disable tethering
+ // when it's already disabled) and never unset after that.
+ if (mBluetoothPanListener == null) {
+ mBluetoothPanListener = new PanServiceListener();
+ adapter.getProfileProxy(mContext, mBluetoothPanListener, BluetoothProfile.PAN);
+ }
+ }
+
+ private class PanServiceListener implements ServiceListener {
+ private boolean mIsConnected = false;
+
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ // Posting this to handling onServiceConnected in tethering handler thread may have
+ // race condition that bluetooth service may disconnected when tethering thread
+ // actaully handle onServiceconnected. If this race happen, calling
+ // BluetoothPan#setBluetoothTethering would silently fail. It is fine because pan
+ // service is unreachable and both bluetooth and bluetooth tethering settings are off.
+ mHandler.post(() -> {
+ mBluetoothPan = (BluetoothPan) proxy;
+ mIsConnected = true;
+
+ for (Pair<Boolean, IIntResultListener> request : mPendingPanRequests) {
+ setBluetoothTetheringSettings(mBluetoothPan, request.first, request.second);
}
- // TODO: Enabling bluetooth tethering can fail asynchronously here.
- // We should figure out a way to bubble up that failure instead of sending success.
- final int result = (((BluetoothPan) proxy).isTetheringOn() == enable)
- ? TETHER_ERROR_NO_ERROR
- : TETHER_ERROR_INTERNAL_ERROR;
- sendTetherResult(listener, result, TETHERING_BLUETOOTH);
- adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
+ mPendingPanRequests.clear();
+ });
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ mHandler.post(() -> {
+ // onServiceDisconnected means Bluetooth is off (or crashed) and is not
+ // reachable before next onServiceConnected.
+ mIsConnected = false;
+
+ for (Pair<Boolean, IIntResultListener> request : mPendingPanRequests) {
+ sendTetherResult(request.second, TETHER_ERROR_SERVICE_UNAVAIL,
+ TETHERING_BLUETOOTH);
+ }
+ mPendingPanRequests.clear();
+ mBluetoothIfaceRequest = null;
+ mBluetoothCallback = null;
+ maybeDisableBluetoothIpServing();
+ });
+ }
+
+ public boolean isConnected() {
+ return mIsConnected;
+ }
+ }
+
+ private void setBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
+ final boolean enable, final IIntResultListener listener) {
+ if (SdkLevel.isAtLeastT()) {
+ changeBluetoothTetheringSettings(bluetoothPan, enable);
+ } else {
+ changeBluetoothTetheringSettingsPreT(bluetoothPan, enable);
+ }
+
+ // Enabling bluetooth tethering settings can silently fail. Send internal error if the
+ // result is not expected.
+ final int result = bluetoothPan.isTetheringOn() == enable
+ ? TETHER_ERROR_NO_ERROR : TETHER_ERROR_INTERNAL_ERROR;
+ sendTetherResult(listener, result, TETHERING_BLUETOOTH);
+ }
+
+ private void changeBluetoothTetheringSettingsPreT(@NonNull final BluetoothPan bluetoothPan,
+ final boolean enable) {
+ bluetoothPan.setBluetoothTethering(enable);
+ }
+
+ private void changeBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
+ final boolean enable) {
+ final BluetoothPanShim panShim = mDeps.getBluetoothPanShim(bluetoothPan);
+ if (enable) {
+ if (mBluetoothIfaceRequest != null) {
+ Log.d(TAG, "Bluetooth tethering settings already enabled");
+ return;
}
- }, BluetoothProfile.PAN);
+
+ mBluetoothCallback = new BluetoothCallback();
+ try {
+ mBluetoothIfaceRequest = panShim.requestTetheredInterface(mExecutor,
+ mBluetoothCallback);
+ } catch (UnsupportedApiLevelException e) {
+ Log.wtf(TAG, "Use unsupported API, " + e);
+ }
+ } else {
+ if (mBluetoothIfaceRequest == null) {
+ Log.d(TAG, "Bluetooth tethering settings already disabled");
+ return;
+ }
+
+ mBluetoothIfaceRequest.release();
+ mBluetoothIfaceRequest = null;
+ mBluetoothCallback = null;
+ // If bluetooth request is released, tethering won't able to receive
+ // onUnavailable callback, explicitly disable bluetooth IpServer manually.
+ maybeDisableBluetoothIpServing();
+ }
+ }
+
+ // BluetoothCallback is only called after T. Before T, PanService would call tether/untether to
+ // notify bluetooth interface status.
+ private class BluetoothCallback implements TetheredInterfaceCallbackShim {
+ @Override
+ public void onAvailable(String iface) {
+ if (this != mBluetoothCallback) return;
+
+ enableIpServing(TETHERING_BLUETOOTH, iface, getRequestedState(TETHERING_BLUETOOTH));
+ mConfiguredBluetoothIface = iface;
+ }
+
+ @Override
+ public void onUnavailable() {
+ if (this != mBluetoothCallback) return;
+
+ maybeDisableBluetoothIpServing();
+ }
+ }
+
+ private void maybeDisableBluetoothIpServing() {
+ if (mConfiguredBluetoothIface == null) return;
+
+ ensureIpServerStopped(mConfiguredBluetoothIface);
+ mConfiguredBluetoothIface = null;
}
private int setEthernetTethering(final boolean enable) {
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 7df9475..c1a747e 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -18,6 +18,7 @@
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothPan;
import android.content.Context;
import android.net.INetd;
import android.net.ip.IpServer;
@@ -31,6 +32,8 @@
import androidx.annotation.NonNull;
import com.android.internal.util.StateMachine;
+import com.android.networkstack.apishim.BluetoothPanShimImpl;
+import com.android.networkstack.apishim.common.BluetoothPanShim;
import java.util.ArrayList;
@@ -158,4 +161,13 @@
TetheringConfiguration cfg) {
return new PrivateAddressCoordinator(ctx, cfg);
}
+
+ /**
+ * Get BluetoothPanShim object to enable/disable bluetooth tethering.
+ *
+ * TODO: use BluetoothPan directly when mainline module is built with API 32.
+ */
+ public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
+ return BluetoothPanShimImpl.newInstance(pan);
+ }
}
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index a2bd1a5..d2188d1 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -48,7 +48,7 @@
// Use with NetworkStackJarJarRules.
android_library {
name: "TetheringIntegrationTestsLatestSdkLib",
- target_sdk_version: "30",
+ target_sdk_version: "31",
platform_apis: true,
defaults: ["TetheringIntegrationTestsDefaults"],
visibility: [
@@ -128,7 +128,7 @@
name: "TetheringCoverageTests",
platform_apis: true,
min_sdk_version: "30",
- target_sdk_version: "30",
+ target_sdk_version: "31",
test_suites: ["device-tests", "mts-tethering"],
test_config: "AndroidTest_Coverage.xml",
defaults: ["libnetworkstackutilsjni_deps"],
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 15f07f2..8bf1a2b 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -25,9 +25,14 @@
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringTester.RemoteResponder;
import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
+import static com.android.net.module.util.HexDump.dumpHexString;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
@@ -47,20 +52,26 @@
import android.net.TetheringManager.StartTetheringCallback;
import android.net.TetheringManager.TetheringEventCallback;
import android.net.TetheringManager.TetheringRequest;
+import android.net.TetheringTester.TetheredDevice;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.PacketBuilder;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.UdpHeader;
import com.android.testutils.HandlerUtils;
import com.android.testutils.TapPacketReader;
import com.android.testutils.TestNetworkTracker;
@@ -71,6 +82,7 @@
import org.junit.runner.RunWith;
import java.io.FileDescriptor;
+import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
@@ -92,10 +104,13 @@
private static final String TAG = EthernetTetheringTest.class.getSimpleName();
private static final int TIMEOUT_MS = 5000;
+ private static final int TETHER_REACHABILITY_ATTEMPTS = 20;
private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
private static final InetAddress TEST_IP6_DNS = parseNumericAddress("2001:db8:1::888");
+ private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
+ ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
private final Context mContext = InstrumentationRegistry.getContext();
private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
@@ -105,6 +120,7 @@
private HandlerThread mHandlerThread;
private Handler mHandler;
private TapPacketReader mDownstreamReader;
+ private TapPacketReader mUpstreamReader;
private TetheredInterfaceRequester mTetheredInterfaceRequester;
private MyTetheringEventCallback mTetheringEventCallback;
@@ -140,6 +156,11 @@
mUpstreamTracker.teardown();
mUpstreamTracker = null;
}
+ if (mUpstreamReader != null) {
+ TapPacketReader reader = mUpstreamReader;
+ mHandler.post(() -> reader.stop());
+ mUpstreamReader = null;
+ }
mTm.stopTethering(TETHERING_ETHERNET);
if (mTetheringEventCallback != null) {
@@ -706,6 +727,168 @@
// TODO: do basic forwarding test here.
}
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
+ //
+ private static final Inet4Address REMOTE_IP4_ADDR =
+ (Inet4Address) parseNumericAddress("8.8.8.8");
+ // Used by public port and private port. Assume port 9876 has not been used yet before the
+ // testing that public port and private port are the same in the testing. Note that NAT port
+ // forwarding could be different between private port and public port.
+ private static final short LOCAL_PORT = 9876;
+ private static final short REMOTE_PORT = 433;
+ private static final byte TYPE_OF_SERVICE = 0;
+ private static final short ID = 27149;
+ private static final short ID2 = 27150;
+ private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
+ private static final byte TIME_TO_LIVE = (byte) 0x40;
+ private static final ByteBuffer PAYLOAD =
+ ByteBuffer.wrap(new byte[] { (byte) 0x12, (byte) 0x34 });
+ private static final ByteBuffer PAYLOAD2 =
+ ByteBuffer.wrap(new byte[] { (byte) 0x56, (byte) 0x78 });
+
+ private boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEther,
+ @NonNull final ByteBuffer payload) {
+ final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
+
+ if (hasEther) {
+ final EthernetHeader etherHeader = Struct.parse(EthernetHeader.class, buf);
+ if (etherHeader == null) return false;
+ }
+
+ final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, buf);
+ if (ipv4Header == null) return false;
+
+ final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf);
+ if (udpHeader == null) return false;
+
+ if (buf.remaining() != payload.limit()) return false;
+
+ return Arrays.equals(Arrays.copyOfRange(buf.array(), buf.position(), buf.limit()),
+ payload.array());
+ }
+
+ @NonNull
+ private ByteBuffer buildUdpv4Packet(@Nullable final MacAddress srcMac,
+ @Nullable final MacAddress dstMac, short id,
+ @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp,
+ short srcPort, short dstPort, @Nullable final ByteBuffer payload)
+ throws Exception {
+ final boolean hasEther = (srcMac != null && dstMac != null);
+ final int payloadLen = (payload == null) ? 0 : payload.limit();
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, IPPROTO_IP, IPPROTO_UDP,
+ payloadLen);
+ final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+
+ if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ETHER_TYPE_IPV4);
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_UDP, srcIp, dstIp);
+ packetBuilder.writeUdpHeader(srcPort, dstPort);
+ if (payload != null) {
+ buffer.put(payload);
+ // in case data might be reused by caller, restore the position and
+ // limit of bytebuffer.
+ payload.clear();
+ }
+
+ return packetBuilder.finalizePacket();
+ }
+
+ @NonNull
+ private ByteBuffer buildUdpv4Packet(short id, @NonNull final Inet4Address srcIp,
+ @NonNull final Inet4Address dstIp, short srcPort, short dstPort,
+ @Nullable final ByteBuffer payload) throws Exception {
+ return buildUdpv4Packet(null /* srcMac */, null /* dstMac */, id, srcIp, dstIp, srcPort,
+ dstPort, payload);
+ }
+
+ // TODO: remove this verification once upstream connected notification race is fixed.
+ // See #runUdp4Test.
+ private boolean isIpv4TetherConnectivityVerified(TetheringTester tester,
+ RemoteResponder remote, TetheredDevice tethered) throws Exception {
+ final ByteBuffer probePacket = buildUdpv4Packet(tethered.macAddr,
+ tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */,
+ TEST_REACHABILITY_PAYLOAD);
+
+ // Send a UDP packet from client and check the packet can be found on upstream interface.
+ for (int i = 0; i < TETHER_REACHABILITY_ATTEMPTS; i++) {
+ tester.sendPacket(probePacket);
+ byte[] expectedPacket = remote.getNextMatchedPacket(p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, TEST_REACHABILITY_PAYLOAD);
+ });
+ if (expectedPacket != null) return true;
+ }
+ return false;
+ }
+
+ private void runUdp4Test(TetheringTester tester, RemoteResponder remote) throws Exception {
+ final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
+ "1:2:3:4:5:6"));
+
+ // TODO: remove the connectivity verification for upstream connected notification race.
+ // Because async upstream connected notification can't guarantee the tethering routing is
+ // ready to use. Need to test tethering connectivity before testing.
+ // For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
+ // from upstream. That can guarantee that the routing is ready. Long term plan is that
+ // refactors upstream connected notification from async to sync.
+ assertTrue(isIpv4TetherConnectivityVerified(tester, remote, tethered));
+
+ // Send a UDP packet in original direction.
+ final ByteBuffer originalPacket = buildUdpv4Packet(tethered.macAddr,
+ tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */,
+ PAYLOAD /* payload */);
+ tester.verifyUpload(remote, originalPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD);
+ });
+
+ // Send a UDP packet in reply direction.
+ final Inet4Address publicIp4Addr = (Inet4Address) TEST_IP4_ADDR.getAddress();
+ final ByteBuffer replyPacket = buildUdpv4Packet(ID2, REMOTE_IP4_ADDR /* srcIp */,
+ publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /*dstPort */,
+ PAYLOAD2 /* payload */);
+ remote.verifyDownload(tester, replyPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2);
+ });
+ }
+
+ @Test
+ public void testUdpV4() throws Exception {
+ assumeFalse(mEm.isAvailable());
+
+ // MyTetheringEventCallback currently only support await first available upstream. Tethering
+ // may select internet network as upstream if test network is not available and not be
+ // preferred yet. Create test upstream network before enable tethering.
+ mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR));
+
+ mDownstreamIface = createTestInterface();
+ mEm.setIncludeTestInterfaces(true);
+
+ final String iface = mTetheredInterfaceRequester.getInterface();
+ assertEquals("TetheredInterfaceCallback for unexpected interface",
+ mDownstreamIface.getInterfaceName(), iface);
+
+ mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName());
+ assertEquals("onUpstreamChanged for unexpected network", mUpstreamTracker.getNetwork(),
+ mTetheringEventCallback.awaitFirstUpstreamConnected());
+
+ mDownstreamReader = makePacketReader(mDownstreamIface);
+ mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
+
+ runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader));
+ }
+
private <T> List<T> toList(T... array) {
return Arrays.asList(array);
}
diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/src/android/net/TetheringTester.java
index 38d74ad..d24661a 100644
--- a/Tethering/tests/integration/src/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/src/android/net/TetheringTester.java
@@ -16,6 +16,12 @@
package android.net;
+import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
+import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import android.net.dhcp.DhcpAckPacket;
@@ -24,13 +30,16 @@
import android.util.ArrayMap;
import android.util.Log;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.arp.ArpPacket;
import com.android.testutils.TapPacketReader;
import java.net.Inet4Address;
import java.nio.ByteBuffer;
import java.util.Random;
import java.util.concurrent.TimeoutException;
-import java.util.function.Function;
+import java.util.function.Predicate;
/**
* A class simulate tethered client. When caller create TetheringTester, it would connect to
@@ -72,15 +81,17 @@
}
public class TetheredDevice {
- private final MacAddress mMacAddr;
-
- public final Inet4Address mIpv4Addr;
+ public final MacAddress macAddr;
+ public final MacAddress routerMacAddr;
+ public final Inet4Address ipv4Addr;
private TetheredDevice(MacAddress mac) throws Exception {
- mMacAddr = mac;
+ macAddr = mac;
- DhcpResults dhcpResults = runDhcp(mMacAddr.toByteArray());
- mIpv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
+ DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
+ ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
+ routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
+ dhcpResults.serverAddress);
}
}
@@ -129,27 +140,121 @@
mDownstreamReader.sendResponse(packet);
}
- private DhcpPacket getNextDhcpPacket() {
- return getNextMatchedPacket((p) -> {
+ private DhcpPacket getNextDhcpPacket() throws Exception {
+ final byte[] packet = getNextMatchedPacket((p) -> {
+ // Test whether this is DHCP packet.
try {
- return DhcpPacket.decodeFullPacket(p, p.length, DhcpPacket.ENCAP_L2);
+ DhcpPacket.decodeFullPacket(p, p.length, DhcpPacket.ENCAP_L2);
} catch (DhcpPacket.ParseException e) {
- // Not a DHCP packet. Continue.
+ // Not a DHCP packet.
+ return false;
}
- return null;
+ return true;
});
+
+ return packet == null ? null :
+ DhcpPacket.decodeFullPacket(packet, packet.length, DhcpPacket.ENCAP_L2);
}
- private <R> R getNextMatchedPacket(Function<byte[], R> match) {
- byte[] packet;
- R result;
- while ((packet = mDownstreamReader.popPacket(PACKET_READ_TIMEOUT_MS)) != null) {
- result = match.apply(packet);
+ @Nullable
+ private ArpPacket parseArpPacket(final byte[] packet) {
+ try {
+ return ArpPacket.parseArpPacket(packet, packet.length);
+ } catch (ArpPacket.ParseException e) {
+ return null;
+ }
+ }
- if (result != null) return result;
+ private void maybeReplyArp(byte[] packet) {
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+
+ final ArpPacket arpPacket = parseArpPacket(packet);
+ if (arpPacket == null || arpPacket.opCode != ARP_REQUEST) return;
+
+ for (int i = 0; i < mTetheredDevices.size(); i++) {
+ TetheredDevice tethered = mTetheredDevices.valueAt(i);
+ if (!arpPacket.targetIp.equals(tethered.ipv4Addr)) continue;
+
+ final ByteBuffer arpReply = ArpPacket.buildArpPacket(
+ arpPacket.senderHwAddress.toByteArray() /* dst */,
+ tethered.macAddr.toByteArray() /* srcMac */,
+ arpPacket.senderIp.getAddress() /* target IP */,
+ arpPacket.senderHwAddress.toByteArray() /* target HW address */,
+ tethered.ipv4Addr.getAddress() /* sender IP */,
+ (short) ARP_REPLY);
+ try {
+ sendPacket(arpReply);
+ } catch (Exception e) {
+ fail("Failed to reply ARP for " + tethered.ipv4Addr);
+ }
+ return;
+ }
+ }
+
+ private MacAddress getRouterMacAddressFromArp(final Inet4Address tetherIp,
+ final MacAddress tetherMac, final Inet4Address routerIp) throws Exception {
+ final ByteBuffer arpProbe = ArpPacket.buildArpPacket(ETHER_BROADCAST /* dst */,
+ tetherMac.toByteArray() /* srcMac */, routerIp.getAddress() /* target IP */,
+ new byte[ETHER_ADDR_LEN] /* target HW address */,
+ tetherIp.getAddress() /* sender IP */, (short) ARP_REQUEST);
+ sendPacket(arpProbe);
+
+ final byte[] packet = getNextMatchedPacket((p) -> {
+ final ArpPacket arpPacket = parseArpPacket(p);
+ if (arpPacket == null || arpPacket.opCode != ARP_REPLY) return false;
+ return arpPacket.targetIp.equals(tetherIp);
+ });
+
+ if (packet != null) {
+ Log.d(TAG, "Get Mac address from ARP");
+ final ArpPacket arpReply = ArpPacket.parseArpPacket(packet, packet.length);
+ return arpReply.senderHwAddress;
+ }
+
+ fail("Could not get ARP packet");
+ return null;
+ }
+
+ public void sendPacket(ByteBuffer packet) throws Exception {
+ mDownstreamReader.sendResponse(packet);
+ }
+
+ public byte[] getNextMatchedPacket(Predicate<byte[]> filter) {
+ byte[] packet;
+ while ((packet = mDownstreamReader.poll(PACKET_READ_TIMEOUT_MS)) != null) {
+ if (filter.test(packet)) return packet;
+
+ maybeReplyArp(packet);
}
return null;
}
+
+ public void verifyUpload(final RemoteResponder dst, final ByteBuffer packet,
+ final Predicate<byte[]> filter) throws Exception {
+ sendPacket(packet);
+ assertNotNull("Upload fail", dst.getNextMatchedPacket(filter));
+ }
+
+ public static class RemoteResponder {
+ final TapPacketReader mUpstreamReader;
+ public RemoteResponder(TapPacketReader reader) {
+ mUpstreamReader = reader;
+ }
+
+ public void sendPacket(ByteBuffer packet) throws Exception {
+ mUpstreamReader.sendResponse(packet);
+ }
+
+ public byte[] getNextMatchedPacket(Predicate<byte[]> filter) throws Exception {
+ return mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS, filter);
+ }
+
+ public void verifyDownload(final TetheringTester dst, final ByteBuffer packet,
+ final Predicate<byte[]> filter) throws Exception {
+ sendPacket(packet);
+ assertNotNull("Download fail", dst.getNextMatchedPacket(filter));
+ }
+ }
}
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index e51d531..18fd63b 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -22,7 +22,7 @@
name: "MtsTetheringTestLatestSdk",
min_sdk_version: "30",
- target_sdk_version: "30",
+ target_sdk_version: "31",
libs: [
"android.test.base",
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index 5150d39..ecd1a39 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -87,7 +87,7 @@
static_libs: [
"TetheringApiStableLib",
],
- target_sdk_version: "30",
+ target_sdk_version: "31",
visibility: [
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 2f2cde0..267c376 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -16,6 +16,7 @@
package android.net.ip;
+import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
@@ -33,6 +34,7 @@
import static android.net.ip.IpServer.STATE_UNAVAILABLE;
import static android.system.OsConstants.ETH_P_IPV6;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH;
@@ -400,11 +402,16 @@
}
@Test
- public void canBeTethered() throws Exception {
+ public void canBeTetheredAsBluetooth() throws Exception {
initStateMachine(TETHERING_BLUETOOTH);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
- InOrder inOrder = inOrder(mCallback, mNetd);
+ InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
+ if (isAtLeastT()) {
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+ IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+ }
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
// One for ipv4 route, one for ipv6 link local route.
@@ -426,7 +433,13 @@
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
- inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+ // One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
+ // happen after T. Before T, the interface configuration control in bluetooth side.
+ if (isAtLeastT()) {
+ inOrder.verify(mNetd).interfaceSetCfg(
+ argThat(cfg -> assertContainsFlag(cfg.flags, IF_STATE_DOWN)));
+ }
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> cfg.flags.length == 0));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
@@ -443,7 +456,7 @@
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
- IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+ IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@@ -587,7 +600,8 @@
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
- inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+ inOrder.verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
+ argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
@@ -683,7 +697,11 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- assertDhcpStarted(mBluetoothPrefix);
+ if (isAtLeastT()) {
+ assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress));
+ } else {
+ assertDhcpStarted(mBluetoothPrefix);
+ }
}
@Test
@@ -1371,7 +1389,6 @@
for (String flag : flags) {
if (flag.equals(match)) return true;
}
- fail("Missing flag: " + match);
return false;
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index f45768f..e4dbc7d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -62,6 +62,7 @@
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
@@ -81,6 +82,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Matchers.anyInt;
@@ -182,6 +185,10 @@
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.CollectionUtils;
+import com.android.networkstack.apishim.common.BluetoothPanShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
import com.android.testutils.MiscAsserts;
@@ -261,6 +268,8 @@
@Mock private PackageManager mPackageManager;
@Mock private BluetoothAdapter mBluetoothAdapter;
@Mock private BluetoothPan mBluetoothPan;
+ @Mock private BluetoothPanShim mBluetoothPanShim;
+ @Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -285,6 +294,7 @@
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private SoftApCallback mSoftApCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+ private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
private TestConnectivityManager mCm;
@@ -483,13 +493,23 @@
return false;
}
-
@Override
public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
TetheringConfiguration cfg) {
mPrivateAddressCoordinator = super.getPrivateAddressCoordinator(ctx, cfg);
return mPrivateAddressCoordinator;
}
+
+ @Override
+ public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
+ try {
+ when(mBluetoothPanShim.requestTetheredInterface(
+ any(), any())).thenReturn(mTetheredInterfaceRequestShim);
+ } catch (UnsupportedApiLevelException e) {
+ fail("BluetoothPan#requestTetheredInterface is not supported");
+ }
+ return mBluetoothPanShim;
+ }
}
private static LinkProperties buildUpstreamLinkProperties(String interfaceName,
@@ -2557,11 +2577,49 @@
@Test
public void testBluetoothTethering() throws Exception {
+ // Switch to @IgnoreUpTo(Build.VERSION_CODES.S_V2) when it is available for AOSP.
+ assumeTrue(isAtLeastT());
+
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
- when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+ mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
mLooper.dispatchAll();
- verifySetBluetoothTethering(true);
+ verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
+ result.assertHasResult();
+
+ mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
+ mLooper.dispatchAll();
+ verifyNetdCommandForBtSetup();
+
+ // If PAN disconnect, tethering should also be stopped.
+ mTetheredInterfaceCallbackShim.onUnavailable();
+ mLooper.dispatchAll();
+ verifyNetdCommandForBtTearDown();
+
+ // Tethering could restart if PAN reconnect.
+ mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
+ mLooper.dispatchAll();
+ verifyNetdCommandForBtSetup();
+
+ // Pretend that bluetooth tethering was disabled.
+ mockBluetoothSettings(true /* bluetoothOn */, false /* tetheringOn */);
+ mTethering.stopTethering(TETHERING_BLUETOOTH);
+ mLooper.dispatchAll();
+ verifySetBluetoothTethering(false /* enable */, false /* bindToPanService */);
+
+ verifyNetdCommandForBtTearDown();
+ }
+
+ @Test
+ public void testBluetoothTetheringBeforeT() throws Exception {
+ // Switch to @IgnoreAfter(Build.VERSION_CODES.S_V2) when it is available for AOSP.
+ assumeFalse(isAtLeastT());
+
+ final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mLooper.dispatchAll();
+ verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
result.assertHasResult();
mTethering.interfaceAdded(TEST_BT_IFNAME);
@@ -2574,6 +2632,73 @@
mLooper.dispatchAll();
tetherResult.assertHasResult();
+ verifyNetdCommandForBtSetup();
+
+ // Turning tethering on a second time does not bind to the PAN service again, since it's
+ // already bound.
+ mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
+ final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), secondResult);
+ mLooper.dispatchAll();
+ verifySetBluetoothTethering(true /* enable */, false /* bindToPanService */);
+ secondResult.assertHasResult();
+
+ mockBluetoothSettings(true /* bluetoothOn */, false /* tetheringOn */);
+ mTethering.stopTethering(TETHERING_BLUETOOTH);
+ mLooper.dispatchAll();
+ final ResultListener untetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.untether(TEST_BT_IFNAME, untetherResult);
+ mLooper.dispatchAll();
+ untetherResult.assertHasResult();
+ verifySetBluetoothTethering(false /* enable */, false /* bindToPanService */);
+
+ verifyNetdCommandForBtTearDown();
+ }
+
+ @Test
+ public void testBluetoothServiceDisconnects() throws Exception {
+ final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mLooper.dispatchAll();
+ ServiceListener panListener = verifySetBluetoothTethering(true /* enable */,
+ true /* bindToPanService */);
+ result.assertHasResult();
+
+ mTethering.interfaceAdded(TEST_BT_IFNAME);
+ mLooper.dispatchAll();
+
+ if (isAtLeastT()) {
+ mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
+ mLooper.dispatchAll();
+ } else {
+ mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
+ mTethering.interfaceStatusChanged(TEST_BT_IFNAME, true);
+ final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.tether(TEST_BT_IFNAME, IpServer.STATE_TETHERED, tetherResult);
+ mLooper.dispatchAll();
+ tetherResult.assertHasResult();
+ }
+
+ verifyNetdCommandForBtSetup();
+
+ panListener.onServiceDisconnected(BluetoothProfile.PAN);
+ mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
+ mLooper.dispatchAll();
+
+ verifyNetdCommandForBtTearDown();
+ }
+
+ private void mockBluetoothSettings(boolean bluetoothOn, boolean tetheringOn) {
+ when(mBluetoothAdapter.isEnabled()).thenReturn(bluetoothOn);
+ when(mBluetoothPan.isTetheringOn()).thenReturn(tetheringOn);
+ }
+
+ private void verifyNetdCommandForBtSetup() throws Exception {
+ if (isAtLeastT()) {
+ verify(mNetd).interfaceSetCfg(argThat(cfg -> TEST_BT_IFNAME.equals(cfg.ifName)
+ && assertContainsFlag(cfg.flags, INetd.IF_STATE_UP)));
+ }
verify(mNetd).tetherInterfaceAdd(TEST_BT_IFNAME);
verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
@@ -2584,39 +2709,64 @@
anyString(), anyString());
verifyNoMoreInteractions(mNetd);
reset(mNetd);
+ }
- when(mBluetoothAdapter.isEnabled()).thenReturn(true);
- mTethering.stopTethering(TETHERING_BLUETOOTH);
- mLooper.dispatchAll();
- final ResultListener untetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
- mTethering.untether(TEST_BT_IFNAME, untetherResult);
- mLooper.dispatchAll();
- untetherResult.assertHasResult();
- verifySetBluetoothTethering(false);
+ private boolean assertContainsFlag(String[] flags, String match) {
+ for (String flag : flags) {
+ if (flag.equals(match)) return true;
+ }
+ return false;
+ }
+ private void verifyNetdCommandForBtTearDown() throws Exception {
verify(mNetd).tetherApplyDnsInterfaces();
verify(mNetd).tetherInterfaceRemove(TEST_BT_IFNAME);
verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
- verify(mNetd).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ // One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
+ // happen after T. Before T, the interface configuration control in bluetooth side.
+ verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
+ any(InterfaceConfigurationParcel.class));
verify(mNetd).tetherStop();
verify(mNetd).ipfwdDisableForwarding(TETHERING_NAME);
- verifyNoMoreInteractions(mNetd);
+ reset(mNetd);
}
- private void verifySetBluetoothTethering(final boolean enable) {
- final ArgumentCaptor<ServiceListener> listenerCaptor =
- ArgumentCaptor.forClass(ServiceListener.class);
+ // If bindToPanService is true, this function would return ServiceListener which could notify
+ // PanService is connected or disconnected.
+ private ServiceListener verifySetBluetoothTethering(final boolean enable,
+ final boolean bindToPanService) throws Exception {
+ ServiceListener listener = null;
verify(mBluetoothAdapter).isEnabled();
- verify(mBluetoothAdapter).getProfileProxy(eq(mServiceContext), listenerCaptor.capture(),
- eq(BluetoothProfile.PAN));
- final ServiceListener listener = listenerCaptor.getValue();
- when(mBluetoothPan.isTetheringOn()).thenReturn(enable);
- listener.onServiceConnected(BluetoothProfile.PAN, mBluetoothPan);
- verify(mBluetoothPan).setBluetoothTethering(enable);
+ if (bindToPanService) {
+ final ArgumentCaptor<ServiceListener> listenerCaptor =
+ ArgumentCaptor.forClass(ServiceListener.class);
+ verify(mBluetoothAdapter).getProfileProxy(eq(mServiceContext), listenerCaptor.capture(),
+ eq(BluetoothProfile.PAN));
+ listener = listenerCaptor.getValue();
+ listener.onServiceConnected(BluetoothProfile.PAN, mBluetoothPan);
+ mLooper.dispatchAll();
+ } else {
+ verify(mBluetoothAdapter, never()).getProfileProxy(eq(mServiceContext), any(),
+ anyInt());
+ }
+
+ if (isAtLeastT()) {
+ if (enable) {
+ final ArgumentCaptor<TetheredInterfaceCallbackShim> callbackCaptor =
+ ArgumentCaptor.forClass(TetheredInterfaceCallbackShim.class);
+ verify(mBluetoothPanShim).requestTetheredInterface(any(), callbackCaptor.capture());
+ mTetheredInterfaceCallbackShim = callbackCaptor.getValue();
+ } else {
+ verify(mTetheredInterfaceRequestShim).release();
+ }
+ } else {
+ verify(mBluetoothPan).setBluetoothTethering(enable);
+ }
verify(mBluetoothPan).isTetheringOn();
- verify(mBluetoothAdapter).closeProfileProxy(eq(BluetoothProfile.PAN), eq(mBluetoothPan));
verifyNoMoreInteractions(mBluetoothAdapter, mBluetoothPan);
reset(mBluetoothAdapter, mBluetoothPan);
+
+ return listener;
}
private void runDualStackUsbTethering(final String expectedIface) throws Exception {
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 17eebe0..fa5af49 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -25,6 +25,8 @@
name: "bpf_connectivity_headers",
vendor_available: false,
host_supported: false,
+ header_libs: ["bpf_headers"],
+ export_header_lib_headers: ["bpf_headers"],
export_include_dirs: ["."],
cflags: [
"-Wall",
@@ -40,10 +42,11 @@
// TODO: remove it when NetworkStatsService is moved into the mainline module and no more
// calls to JNI in libservices.core.
"//frameworks/base/services/core/jni",
+ "//packages/modules/Connectivity/service/native/libs/libclat",
"//packages/modules/Connectivity/Tethering",
+ "//packages/modules/Connectivity/service/native",
+ "//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
"//packages/modules/Connectivity/tests/unit/jni",
- // TODO: remove system/netd/* when all BPF code is moved out of Netd.
- "//system/netd/libnetdbpf",
"//system/netd/server",
"//system/netd/tests",
],
@@ -69,3 +72,29 @@
"-Werror",
],
}
+
+bpf {
+ name: "clatd.o_mainline",
+ srcs: ["clatd.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ include_dirs: [
+ "frameworks/libs/net/common/netd/libnetdutils/include",
+ ],
+ sub_dir: "net_shared",
+}
+
+bpf {
+ name: "netd.o_mainline",
+ srcs: ["netd.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ include_dirs: [
+ "frameworks/libs/net/common/netd/libnetdutils/include",
+ ],
+ sub_dir: "net_shared",
+}
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index 8577d9d..f0df97b 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -20,7 +20,6 @@
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/in6.h>
-#include <netdutils/UidConstants.h>
// This header file is shared by eBPF kernel programs (C) and netd (C++) and
// some of the maps are also accessed directly from Java mainline module code.
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
new file mode 100644
index 0000000..dc646c3
--- /dev/null
+++ b/bpf_progs/clatd.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/swab.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+// bionic kernel uapi linux/udp.h header is munged...
+#define __kernel_udphdr udphdr
+#include <linux/udp.h>
+
+#include "bpf_helpers.h"
+#include "bpf_net_helpers.h"
+#include "bpf_shared.h"
+
+// From kernel:include/net/ip.h
+#define IP_DF 0x4000 // Flag: "Don't Fragment"
+
+DEFINE_BPF_MAP_GRW(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16, AID_SYSTEM)
+
+static inline __always_inline int nat64(struct __sk_buff* skb, bool is_ethernet) {
+ const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ const struct ethhdr* const eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ const struct ipv6hdr* const ip6 = is_ethernet ? (void*)(eth + 1) : data;
+
+ // Require ethernet dst mac address to be our unicast address.
+ if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
+
+ // Must be meta-ethernet IPv6 frame
+ if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_PIPE;
+
+ // Must have (ethernet and) ipv6 header
+ if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_PIPE;
+
+ // Ethertype - if present - must be IPv6
+ if (is_ethernet && (eth->h_proto != htons(ETH_P_IPV6))) return TC_ACT_PIPE;
+
+ // IP version must be 6
+ if (ip6->version != 6) return TC_ACT_PIPE;
+
+ // Maximum IPv6 payload length that can be translated to IPv4
+ if (ntohs(ip6->payload_len) > 0xFFFF - sizeof(struct iphdr)) return TC_ACT_PIPE;
+
+ switch (ip6->nexthdr) {
+ case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
+ case IPPROTO_UDP: // address means there is no need to update their checksums.
+ case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers,
+ case IPPROTO_ESP: // since there is never a checksum to update.
+ break;
+
+ default: // do not know how to handle anything else
+ return TC_ACT_PIPE;
+ }
+
+ ClatIngress6Key k = {
+ .iif = skb->ifindex,
+ .pfx96.in6_u.u6_addr32 =
+ {
+ ip6->saddr.in6_u.u6_addr32[0],
+ ip6->saddr.in6_u.u6_addr32[1],
+ ip6->saddr.in6_u.u6_addr32[2],
+ },
+ .local6 = ip6->daddr,
+ };
+
+ ClatIngress6Value* v = bpf_clat_ingress6_map_lookup_elem(&k);
+
+ if (!v) return TC_ACT_PIPE;
+
+ struct ethhdr eth2; // used iff is_ethernet
+ if (is_ethernet) {
+ eth2 = *eth; // Copy over the ethernet header (src/dst mac)
+ eth2.h_proto = htons(ETH_P_IP); // But replace the ethertype
+ }
+
+ struct iphdr ip = {
+ .version = 4, // u4
+ .ihl = sizeof(struct iphdr) / sizeof(__u32), // u4
+ .tos = (ip6->priority << 4) + (ip6->flow_lbl[0] >> 4), // u8
+ .tot_len = htons(ntohs(ip6->payload_len) + sizeof(struct iphdr)), // u16
+ .id = 0, // u16
+ .frag_off = htons(IP_DF), // u16
+ .ttl = ip6->hop_limit, // u8
+ .protocol = ip6->nexthdr, // u8
+ .check = 0, // u16
+ .saddr = ip6->saddr.in6_u.u6_addr32[3], // u32
+ .daddr = v->local4.s_addr, // u32
+ };
+
+ // Calculate the IPv4 one's complement checksum of the IPv4 header.
+ __wsum sum4 = 0;
+ for (int i = 0; i < sizeof(ip) / sizeof(__u16); ++i) {
+ sum4 += ((__u16*)&ip)[i];
+ }
+ // Note that sum4 is guaranteed to be non-zero by virtue of ip.version == 4
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
+ ip.check = (__u16)~sum4; // sum4 cannot be zero, so this is never 0xFFFF
+
+ // Calculate the *negative* IPv6 16-bit one's complement checksum of the IPv6 header.
+ __wsum sum6 = 0;
+ // We'll end up with a non-zero sum due to ip6->version == 6 (which has '0' bits)
+ for (int i = 0; i < sizeof(*ip6) / sizeof(__u16); ++i) {
+ sum6 += ~((__u16*)ip6)[i]; // note the bitwise negation
+ }
+
+ // Note that there is no L4 checksum update: we are relying on the checksum neutrality
+ // of the ipv6 address chosen by netd's ClatdController.
+
+ // Packet mutations begin - point of no return, but if this first modification fails
+ // the packet is probably still pristine, so let clatd handle it.
+ if (bpf_skb_change_proto(skb, htons(ETH_P_IP), 0)) return TC_ACT_PIPE;
+
+ // This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet.
+ //
+ // In such a case, skb->csum is a 16-bit one's complement sum of the entire payload,
+ // thus we need to subtract out the ipv6 header's sum, and add in the ipv4 header's sum.
+ // However, by construction of ip.check above the checksum of an ipv4 header is zero.
+ // Thus we only need to subtract the ipv6 header's sum, which is the same as adding
+ // in the sum of the bitwise negation of the ipv6 header.
+ //
+ // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error
+ // (-ENOTSUPP) if it isn't. So we just ignore the return code.
+ //
+ // if (skb->ip_summed == CHECKSUM_COMPLETE)
+ // return (skb->csum = csum_add(skb->csum, csum));
+ // else
+ // return -ENOTSUPP;
+ bpf_csum_update(skb, sum6);
+
+ // bpf_skb_change_proto() invalidates all pointers - reload them.
+ data = (void*)(long)skb->data;
+ data_end = (void*)(long)skb->data_end;
+
+ // I cannot think of any valid way for this error condition to trigger, however I do
+ // believe the explicit check is required to keep the in kernel ebpf verifier happy.
+ if (data + l2_header_size + sizeof(struct iphdr) > data_end) return TC_ACT_SHOT;
+
+ if (is_ethernet) {
+ struct ethhdr* new_eth = data;
+
+ // Copy over the updated ethernet header
+ *new_eth = eth2;
+
+ // Copy over the new ipv4 header.
+ *(struct iphdr*)(new_eth + 1) = ip;
+ } else {
+ // Copy over the new ipv4 header without an ethernet header.
+ *(struct iphdr*)data = ip;
+ }
+
+ // Redirect, possibly back to same interface, so tcpdump sees packet twice.
+ if (v->oif) return bpf_redirect(v->oif, BPF_F_INGRESS);
+
+ // Just let it through, tcpdump will not see IPv4 packet.
+ return TC_ACT_PIPE;
+}
+
+DEFINE_BPF_PROG("schedcls/ingress6/clat_ether", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_ether)
+(struct __sk_buff* skb) {
+ return nat64(skb, true);
+}
+
+DEFINE_BPF_PROG("schedcls/ingress6/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_rawip)
+(struct __sk_buff* skb) {
+ return nat64(skb, false);
+}
+
+DEFINE_BPF_MAP_GRW(clat_egress4_map, HASH, ClatEgress4Key, ClatEgress4Value, 16, AID_SYSTEM)
+
+DEFINE_BPF_PROG("schedcls/egress4/clat_ether", AID_ROOT, AID_SYSTEM, sched_cls_egress4_clat_ether)
+(struct __sk_buff* skb) {
+ return TC_ACT_PIPE;
+}
+
+DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_egress4_clat_rawip)
+(struct __sk_buff* skb) {
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ const struct iphdr* const ip4 = data;
+
+ // Must be meta-ethernet IPv4 frame
+ if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
+
+ // Must have ipv4 header
+ if (data + sizeof(*ip4) > data_end) return TC_ACT_PIPE;
+
+ // IP version must be 4
+ if (ip4->version != 4) return TC_ACT_PIPE;
+
+ // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+ if (ip4->ihl != 5) return TC_ACT_PIPE;
+
+ // Calculate the IPv4 one's complement checksum of the IPv4 header.
+ __wsum sum4 = 0;
+ for (int i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) {
+ sum4 += ((__u16*)ip4)[i];
+ }
+ // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
+ // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
+ if (sum4 != 0xFFFF) return TC_ACT_PIPE;
+
+ // Minimum IPv4 total length is the size of the header
+ if (ntohs(ip4->tot_len) < sizeof(*ip4)) return TC_ACT_PIPE;
+
+ // We are incapable of dealing with IPv4 fragments
+ if (ip4->frag_off & ~htons(IP_DF)) return TC_ACT_PIPE;
+
+ switch (ip4->protocol) {
+ case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
+ case IPPROTO_GRE: // address means there is no need to update their checksums.
+ case IPPROTO_ESP: // We do not need to bother looking at GRE/ESP headers,
+ break; // since there is never a checksum to update.
+
+ case IPPROTO_UDP: // See above comment, but must also have UDP header...
+ if (data + sizeof(*ip4) + sizeof(struct udphdr) > data_end) return TC_ACT_PIPE;
+ const struct udphdr* uh = (const struct udphdr*)(ip4 + 1);
+ // If IPv4/UDP checksum is 0 then fallback to clatd so it can calculate the
+ // checksum. Otherwise the network or more likely the NAT64 gateway might
+ // drop the packet because in most cases IPv6/UDP packets with a zero checksum
+ // are invalid. See RFC 6935. TODO: calculate checksum via bpf_csum_diff()
+ if (!uh->check) return TC_ACT_PIPE;
+ break;
+
+ default: // do not know how to handle anything else
+ return TC_ACT_PIPE;
+ }
+
+ ClatEgress4Key k = {
+ .iif = skb->ifindex,
+ .local4.s_addr = ip4->saddr,
+ };
+
+ ClatEgress4Value* v = bpf_clat_egress4_map_lookup_elem(&k);
+
+ if (!v) return TC_ACT_PIPE;
+
+ // Translating without redirecting doesn't make sense.
+ if (!v->oif) return TC_ACT_PIPE;
+
+ // This implementation is currently limited to rawip.
+ if (v->oifIsEthernet) return TC_ACT_PIPE;
+
+ struct ipv6hdr ip6 = {
+ .version = 6, // __u8:4
+ .priority = ip4->tos >> 4, // __u8:4
+ .flow_lbl = {(ip4->tos & 0xF) << 4, 0, 0}, // __u8[3]
+ .payload_len = htons(ntohs(ip4->tot_len) - 20), // __be16
+ .nexthdr = ip4->protocol, // __u8
+ .hop_limit = ip4->ttl, // __u8
+ .saddr = v->local6, // struct in6_addr
+ .daddr = v->pfx96, // struct in6_addr
+ };
+ ip6.daddr.in6_u.u6_addr32[3] = ip4->daddr;
+
+ // Calculate the IPv6 16-bit one's complement checksum of the IPv6 header.
+ __wsum sum6 = 0;
+ // We'll end up with a non-zero sum due to ip6.version == 6
+ for (int i = 0; i < sizeof(ip6) / sizeof(__u16); ++i) {
+ sum6 += ((__u16*)&ip6)[i];
+ }
+
+ // Note that there is no L4 checksum update: we are relying on the checksum neutrality
+ // of the ipv6 address chosen by netd's ClatdController.
+
+ // Packet mutations begin - point of no return, but if this first modification fails
+ // the packet is probably still pristine, so let clatd handle it.
+ if (bpf_skb_change_proto(skb, htons(ETH_P_IPV6), 0)) return TC_ACT_PIPE;
+
+ // This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet.
+ //
+ // In such a case, skb->csum is a 16-bit one's complement sum of the entire payload,
+ // thus we need to subtract out the ipv4 header's sum, and add in the ipv6 header's sum.
+ // However, we've already verified the ipv4 checksum is correct and thus 0.
+ // Thus we only need to add the ipv6 header's sum.
+ //
+ // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error
+ // (-ENOTSUPP) if it isn't. So we just ignore the return code (see above for more details).
+ bpf_csum_update(skb, sum6);
+
+ // bpf_skb_change_proto() invalidates all pointers - reload them.
+ data = (void*)(long)skb->data;
+ data_end = (void*)(long)skb->data_end;
+
+ // I cannot think of any valid way for this error condition to trigger, however I do
+ // believe the explicit check is required to keep the in kernel ebpf verifier happy.
+ if (data + sizeof(ip6) > data_end) return TC_ACT_SHOT;
+
+ // Copy over the new ipv6 header without an ethernet header.
+ *(struct ipv6hdr*)data = ip6;
+
+ // Redirect to non v4-* interface. Tcpdump only sees packet after this redirect.
+ return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("netd");
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
new file mode 100644
index 0000000..f0af8b4
--- /dev/null
+++ b/bpf_progs/netd.c
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <bpf_helpers.h>
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/tcp.h>
+#include <netdutils/UidConstants.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "bpf_net_helpers.h"
+#include "bpf_shared.h"
+
+// This is defined for cgroup bpf filter only.
+#define BPF_DROP_UNLESS_DNS 2
+#define BPF_PASS 1
+#define BPF_DROP 0
+
+// This is used for xt_bpf program only.
+#define BPF_NOMATCH 0
+#define BPF_MATCH 1
+
+#define BPF_EGRESS 0
+#define BPF_INGRESS 1
+
+#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
+#define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
+#define IPPROTO_IHL_OFF 0
+#define TCP_FLAG_OFF 13
+#define RST_OFFSET 2
+
+DEFINE_BPF_MAP_GRW(cookie_tag_map, HASH, uint64_t, UidTagValue, COOKIE_UID_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(uid_counterset_map, HASH, uint32_t, uint8_t, UID_COUNTERSET_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(app_uid_stats_map, HASH, uint32_t, StatsValue, APP_STATS_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(stats_map_A, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint8_t, CONFIGURATION_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE, AID_NET_BW_ACCT)
+
+/* never actually used from ebpf */
+DEFINE_BPF_MAP_GRW(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE,
+ AID_NET_BW_ACCT)
+
+static __always_inline int is_system_uid(uint32_t uid) {
+ return (uid <= MAX_SYSTEM_UID) && (uid >= MIN_SYSTEM_UID);
+}
+
+/*
+ * Note: this blindly assumes an MTU of 1500, and that packets > MTU are always TCP,
+ * and that TCP is using the Linux default settings with TCP timestamp option enabled
+ * which uses 12 TCP option bytes per frame.
+ *
+ * These are not unreasonable assumptions:
+ *
+ * The internet does not really support MTUs greater than 1500, so most TCP traffic will
+ * be at that MTU, or slightly below it (worst case our upwards adjustment is too small).
+ *
+ * The chance our traffic isn't IP at all is basically zero, so the IP overhead correction
+ * is bound to be needed.
+ *
+ * Furthermore, the likelyhood that we're having to deal with GSO (ie. > MTU) packets that
+ * are not IP/TCP is pretty small (few other things are supported by Linux) and worse case
+ * our extra overhead will be slightly off, but probably still better than assuming none.
+ *
+ * Most servers are also Linux and thus support/default to using TCP timestamp option
+ * (and indeed TCP timestamp option comes from RFC 1323 titled "TCP Extensions for High
+ * Performance" which also defined TCP window scaling and are thus absolutely ancient...).
+ *
+ * All together this should be more correct than if we simply ignored GSO frames
+ * (ie. counted them as single packets with no extra overhead)
+ *
+ * Especially since the number of packets is important for any future clat offload correction.
+ * (which adjusts upward by 20 bytes per packet to account for ipv4 -> ipv6 header conversion)
+ */
+#define DEFINE_UPDATE_STATS(the_stats_map, TypeOfKey) \
+ static __always_inline inline void update_##the_stats_map(struct __sk_buff* skb, \
+ int direction, TypeOfKey* key) { \
+ StatsValue* value = bpf_##the_stats_map##_lookup_elem(key); \
+ if (!value) { \
+ StatsValue newValue = {}; \
+ bpf_##the_stats_map##_update_elem(key, &newValue, BPF_NOEXIST); \
+ value = bpf_##the_stats_map##_lookup_elem(key); \
+ } \
+ if (value) { \
+ const int mtu = 1500; \
+ uint64_t packets = 1; \
+ uint64_t bytes = skb->len; \
+ if (bytes > mtu) { \
+ bool is_ipv6 = (skb->protocol == htons(ETH_P_IPV6)); \
+ int ip_overhead = (is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr)); \
+ int tcp_overhead = ip_overhead + sizeof(struct tcphdr) + 12; \
+ int mss = mtu - tcp_overhead; \
+ uint64_t payload = bytes - tcp_overhead; \
+ packets = (payload + mss - 1) / mss; \
+ bytes = tcp_overhead * packets + payload; \
+ } \
+ if (direction == BPF_EGRESS) { \
+ __sync_fetch_and_add(&value->txPackets, packets); \
+ __sync_fetch_and_add(&value->txBytes, bytes); \
+ } else if (direction == BPF_INGRESS) { \
+ __sync_fetch_and_add(&value->rxPackets, packets); \
+ __sync_fetch_and_add(&value->rxBytes, bytes); \
+ } \
+ } \
+ }
+
+DEFINE_UPDATE_STATS(app_uid_stats_map, uint32_t)
+DEFINE_UPDATE_STATS(iface_stats_map, uint32_t)
+DEFINE_UPDATE_STATS(stats_map_A, StatsKey)
+DEFINE_UPDATE_STATS(stats_map_B, StatsKey)
+
+static inline bool skip_owner_match(struct __sk_buff* skb) {
+ int offset = -1;
+ int ret = 0;
+ if (skb->protocol == htons(ETH_P_IP)) {
+ offset = IP_PROTO_OFF;
+ uint8_t proto, ihl;
+ uint8_t flag;
+ ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+ if (!ret) {
+ if (proto == IPPROTO_ESP) {
+ return true;
+ } else if (proto == IPPROTO_TCP) {
+ ret = bpf_skb_load_bytes(skb, IPPROTO_IHL_OFF, &ihl, 1);
+ ihl = ihl & 0x0F;
+ ret = bpf_skb_load_bytes(skb, ihl * 4 + TCP_FLAG_OFF, &flag, 1);
+ if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+ return true;
+ }
+ }
+ }
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ offset = IPV6_PROTO_OFF;
+ uint8_t proto;
+ ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+ if (!ret) {
+ if (proto == IPPROTO_ESP) {
+ return true;
+ } else if (proto == IPPROTO_TCP) {
+ uint8_t flag;
+ ret = bpf_skb_load_bytes(skb, sizeof(struct ipv6hdr) + TCP_FLAG_OFF, &flag, 1);
+ if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static __always_inline BpfConfig getConfig(uint32_t configKey) {
+ uint32_t mapSettingKey = configKey;
+ BpfConfig* config = bpf_configuration_map_lookup_elem(&mapSettingKey);
+ if (!config) {
+ // Couldn't read configuration entry. Assume everything is disabled.
+ return DEFAULT_CONFIG;
+ }
+ return *config;
+}
+
+static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, int direction) {
+ if (skip_owner_match(skb)) return BPF_PASS;
+
+ if (is_system_uid(uid)) return BPF_PASS;
+
+ BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
+
+ UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
+ uint8_t uidRules = uidEntry ? uidEntry->rule : 0;
+ uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
+
+ if (enabledRules) {
+ if ((enabledRules & DOZABLE_MATCH) && !(uidRules & DOZABLE_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & STANDBY_MATCH) && (uidRules & STANDBY_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & POWERSAVE_MATCH) && !(uidRules & POWERSAVE_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & RESTRICTED_MATCH) && !(uidRules & RESTRICTED_MATCH)) {
+ return BPF_DROP;
+ }
+ }
+ if (direction == BPF_INGRESS && (uidRules & IIF_MATCH)) {
+ // Drops packets not coming from lo nor the allowlisted interface
+ if (allowed_iif && skb->ifindex != 1 && skb->ifindex != allowed_iif) {
+ return BPF_DROP_UNLESS_DNS;
+ }
+ }
+ return BPF_PASS;
+}
+
+static __always_inline inline void update_stats_with_config(struct __sk_buff* skb, int direction,
+ StatsKey* key, uint8_t selectedMap) {
+ if (selectedMap == SELECT_MAP_A) {
+ update_stats_map_A(skb, direction, key);
+ } else if (selectedMap == SELECT_MAP_B) {
+ update_stats_map_B(skb, direction, key);
+ }
+}
+
+static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, int direction) {
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ uint64_t cookie = bpf_get_socket_cookie(skb);
+ UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
+ uint32_t uid, tag;
+ if (utag) {
+ uid = utag->uid;
+ tag = utag->tag;
+ } else {
+ uid = sock_uid;
+ tag = 0;
+ }
+
+ // Always allow and never count clat traffic. Only the IPv4 traffic on the stacked
+ // interface is accounted for and subject to usage restrictions.
+ // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
+ if (sock_uid == AID_CLAT || uid == AID_CLAT) {
+ return BPF_PASS;
+ }
+
+ int match = bpf_owner_match(skb, sock_uid, direction);
+ if ((direction == BPF_EGRESS) && (match == BPF_DROP)) {
+ // If an outbound packet is going to be dropped, we do not count that
+ // traffic.
+ return match;
+ }
+
+// Workaround for secureVPN with VpnIsolation enabled, refer to b/159994981 for details.
+// Keep TAG_SYSTEM_DNS in sync with DnsResolver/include/netd_resolv/resolv.h
+// and TrafficStatsConstants.java
+#define TAG_SYSTEM_DNS 0xFFFFFF82
+ if (tag == TAG_SYSTEM_DNS && uid == AID_DNS) {
+ uid = sock_uid;
+ if (match == BPF_DROP_UNLESS_DNS) match = BPF_PASS;
+ } else {
+ if (match == BPF_DROP_UNLESS_DNS) match = BPF_DROP;
+ }
+
+ StatsKey key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
+
+ uint8_t* counterSet = bpf_uid_counterset_map_lookup_elem(&uid);
+ if (counterSet) key.counterSet = (uint32_t)*counterSet;
+
+ uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+ uint8_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
+
+ // Use asm("%0 &= 1" : "+r"(match)) before return match,
+ // to help kernel's bpf verifier, so that it can be 100% certain
+ // that the returned value is always BPF_NOMATCH(0) or BPF_MATCH(1).
+ if (!selectedMap) {
+ asm("%0 &= 1" : "+r"(match));
+ return match;
+ }
+
+ if (key.tag) {
+ update_stats_with_config(skb, direction, &key, *selectedMap);
+ key.tag = 0;
+ }
+
+ update_stats_with_config(skb, direction, &key, *selectedMap);
+ update_app_uid_stats_map(skb, direction, &uid);
+ asm("%0 &= 1" : "+r"(match));
+ return match;
+}
+
+DEFINE_BPF_PROG("cgroupskb/ingress/stats", AID_ROOT, AID_ROOT, bpf_cgroup_ingress)
+(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, BPF_INGRESS);
+}
+
+DEFINE_BPF_PROG("cgroupskb/egress/stats", AID_ROOT, AID_ROOT, bpf_cgroup_egress)
+(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, BPF_EGRESS);
+}
+
+DEFINE_BPF_PROG("skfilter/egress/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_egress_prog)
+(struct __sk_buff* skb) {
+ // Clat daemon does not generate new traffic, all its traffic is accounted for already
+ // on the v4-* interfaces (except for the 20 (or 28) extra bytes of IPv6 vs IPv4 overhead,
+ // but that can be corrected for later when merging v4-foo stats into interface foo's).
+ // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ if (sock_uid == AID_CLAT) return BPF_NOMATCH;
+ if (sock_uid == AID_SYSTEM) {
+ uint64_t cookie = bpf_get_socket_cookie(skb);
+ UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
+ if (utag && utag->uid == AID_CLAT) return BPF_NOMATCH;
+ }
+
+ uint32_t key = skb->ifindex;
+ update_iface_stats_map(skb, BPF_EGRESS, &key);
+ return BPF_MATCH;
+}
+
+DEFINE_BPF_PROG("skfilter/ingress/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_ingress_prog)
+(struct __sk_buff* skb) {
+ // Clat daemon traffic is not accounted by virtue of iptables raw prerouting drop rule
+ // (in clat_raw_PREROUTING chain), which triggers before this (in bw_raw_PREROUTING chain).
+ // It will be accounted for on the v4-* clat interface instead.
+ // Keep that in mind when moving this out of iptables xt_bpf and into tc ingress (or xdp).
+
+ uint32_t key = skb->ifindex;
+ update_iface_stats_map(skb, BPF_INGRESS, &key);
+ return BPF_MATCH;
+}
+
+DEFINE_BPF_PROG("schedact/ingress/account", AID_ROOT, AID_NET_ADMIN, tc_bpf_ingress_account_prog)
+(struct __sk_buff* skb) {
+ // Account for ingress traffic before tc drops it.
+ uint32_t key = skb->ifindex;
+ update_iface_stats_map(skb, BPF_INGRESS, &key);
+ return TC_ACT_UNSPEC;
+}
+
+DEFINE_BPF_PROG("skfilter/allowlist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_allowlist_prog)
+(struct __sk_buff* skb) {
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ if (is_system_uid(sock_uid)) return BPF_MATCH;
+
+ // 65534 is the overflow 'nobody' uid, usually this being returned means
+ // that skb->sk is NULL during RX (early decap socket lookup failure),
+ // which commonly happens for incoming packets to an unconnected udp socket.
+ // Additionally bpf_get_socket_cookie() returns 0 if skb->sk is NULL
+ if ((sock_uid == 65534) && !bpf_get_socket_cookie(skb) && is_received_skb(skb))
+ return BPF_MATCH;
+
+ UidOwnerValue* allowlistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
+ if (allowlistMatch) return allowlistMatch->rule & HAPPY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
+ return BPF_NOMATCH;
+}
+
+DEFINE_BPF_PROG("skfilter/denylist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_denylist_prog)
+(struct __sk_buff* skb) {
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ UidOwnerValue* denylistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
+ if (denylistMatch) return denylistMatch->rule & PENALTY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
+ return BPF_NOMATCH;
+}
+
+DEFINE_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
+ KVER(4, 14, 0))
+(struct bpf_sock* sk) {
+ uint64_t gid_uid = bpf_get_current_uid_gid();
+ /*
+ * A given app is guaranteed to have the same app ID in all the profiles in
+ * which it is installed, and install permission is granted to app for all
+ * user at install time so we only check the appId part of a request uid at
+ * run time. See UserHandle#isSameApp for detail.
+ */
+ uint32_t appId = (gid_uid & 0xffffffff) % PER_USER_RANGE;
+ uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
+ if (!permissions) {
+ // UID not in map. Default to just INTERNET permission.
+ return 1;
+ }
+
+ // A return value of 1 means allow, everything else means deny.
+ return (*permissions & BPF_PERMISSION_INTERNET) == BPF_PERMISSION_INTERNET;
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("netd");
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
new file mode 100644
index 0000000..abcfbeb
--- /dev/null
+++ b/framework-t/Android.bp
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+ name: "enable-framework-connectivity-t-targets",
+ enabled: true,
+}
+// The above defaults can be used to disable framework-connectivity t
+// targets while minimizing merge conflicts in the build rules.
+
+
+java_sdk_library {
+ name: "framework-connectivity-tiramisu",
+ sdk_version: "module_current",
+ min_sdk_version: "Tiramisu",
+ defaults: [
+ "framework-module-defaults",
+ "enable-framework-connectivity-t-targets",
+ ],
+ srcs: [
+ ":framework-connectivity-tiramisu-updatable-sources",
+ ],
+ libs: [
+ "unsupportedappusage",
+ "app-compat-annotations",
+ ],
+ permitted_packages: [
+ "android.net",
+ "android.net.nsd",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+ impl_library_visibility: [
+ "//packages/modules/Connectivity/Tethering/apex",
+ // In preparation for future move
+ "//packages/modules/Connectivity/apex",
+ "//packages/modules/Connectivity/service-t",
+ "//frameworks/base",
+
+ // Tests using hidden APIs
+ "//cts/tests/netlegacy22.api",
+ "//external/sl4a:__subpackages__",
+ "//frameworks/libs/net/common/testutils",
+ "//frameworks/libs/net/common/tests:__subpackages__",
+ "//frameworks/opt/telephony/tests/telephonytests",
+ "//packages/modules/CaptivePortalLogin/tests",
+ "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
+ "//packages/modules/Connectivity/tests:__subpackages__",
+ "//packages/modules/NetworkStack/tests:__subpackages__",
+ "//packages/modules/Wifi/service/tests/wifitests",
+ ],
+}
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
new file mode 100644
index 0000000..0443456
--- /dev/null
+++ b/framework-t/api/current.txt
@@ -0,0 +1,60 @@
+// Signature format: 2.0
+package android.net.nsd {
+
+ public final class NsdManager {
+ method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
+ method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
+ method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
+ method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
+ method public void unregisterService(android.net.nsd.NsdManager.RegistrationListener);
+ field public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
+ field public static final String EXTRA_NSD_STATE = "nsd_state";
+ field public static final int FAILURE_ALREADY_ACTIVE = 3; // 0x3
+ field public static final int FAILURE_INTERNAL_ERROR = 0; // 0x0
+ field public static final int FAILURE_MAX_LIMIT = 4; // 0x4
+ field public static final int NSD_STATE_DISABLED = 1; // 0x1
+ field public static final int NSD_STATE_ENABLED = 2; // 0x2
+ field public static final int PROTOCOL_DNS_SD = 1; // 0x1
+ }
+
+ public static interface NsdManager.DiscoveryListener {
+ method public void onDiscoveryStarted(String);
+ method public void onDiscoveryStopped(String);
+ method public void onServiceFound(android.net.nsd.NsdServiceInfo);
+ method public void onServiceLost(android.net.nsd.NsdServiceInfo);
+ method public void onStartDiscoveryFailed(String, int);
+ method public void onStopDiscoveryFailed(String, int);
+ }
+
+ public static interface NsdManager.RegistrationListener {
+ method public void onRegistrationFailed(android.net.nsd.NsdServiceInfo, int);
+ method public void onServiceRegistered(android.net.nsd.NsdServiceInfo);
+ method public void onServiceUnregistered(android.net.nsd.NsdServiceInfo);
+ method public void onUnregistrationFailed(android.net.nsd.NsdServiceInfo, int);
+ }
+
+ public static interface NsdManager.ResolveListener {
+ method public void onResolveFailed(android.net.nsd.NsdServiceInfo, int);
+ method public void onServiceResolved(android.net.nsd.NsdServiceInfo);
+ }
+
+ public final class NsdServiceInfo implements android.os.Parcelable {
+ ctor public NsdServiceInfo();
+ method public int describeContents();
+ method public java.util.Map<java.lang.String,byte[]> getAttributes();
+ method public java.net.InetAddress getHost();
+ method public int getPort();
+ method public String getServiceName();
+ method public String getServiceType();
+ method public void removeAttribute(String);
+ method public void setAttribute(String, String);
+ method public void setHost(java.net.InetAddress);
+ method public void setPort(int);
+ method public void setServiceName(String);
+ method public void setServiceType(String);
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.NsdServiceInfo> CREATOR;
+ }
+
+}
+
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
new file mode 100644
index 0000000..81d89c6
--- /dev/null
+++ b/framework-t/api/module-lib-current.txt
@@ -0,0 +1,9 @@
+// Signature format: 2.0
+package android.net {
+
+ public final class ConnectivityFrameworkInitializerTiramisu {
+ method public static void registerServiceWrappers();
+ }
+
+}
+
diff --git a/framework-t/api/module-lib-removed.txt b/framework-t/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-t/api/removed.txt b/framework-t/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/api/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-t/api/system-removed.txt b/framework-t/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework/Android.bp b/framework/Android.bp
index d31f74f..de505c7 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -66,6 +66,7 @@
":net-utils-framework-common-srcs",
],
aidl: {
+ generate_get_transaction_name: true,
include_dirs: [
// Include directories for parcelables that are part of the stable API, and need a
// one-line "parcelable X" .aidl declaration to be used in AIDL interfaces.
@@ -93,6 +94,7 @@
// In preparation for future move
"//packages/modules/Connectivity/apex",
"//packages/modules/Connectivity/service",
+ "//packages/modules/Connectivity/service-t",
"//frameworks/base/packages/Connectivity/service",
"//frameworks/base",
@@ -112,6 +114,7 @@
apex_available: [
"com.android.tethering",
],
+ lint: { strict_updatability_linting: true },
}
cc_library_shared {
diff --git a/framework/aidl-export/android/net/DhcpOption.aidl b/framework/aidl-export/android/net/DhcpOption.aidl
new file mode 100644
index 0000000..9ed0e62
--- /dev/null
+++ b/framework/aidl-export/android/net/DhcpOption.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable DhcpOption;
+
diff --git a/framework/aidl-export/android/net/ProfileNetworkPreference.aidl b/framework/aidl-export/android/net/ProfileNetworkPreference.aidl
new file mode 100644
index 0000000..d7f2402
--- /dev/null
+++ b/framework/aidl-export/android/net/ProfileNetworkPreference.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable ProfileNetworkPreference;
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 33f4d14..a373b71 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -196,6 +196,7 @@
}
public static class DnsResolver.DnsException extends java.lang.Exception {
+ ctor public DnsResolver.DnsException(int, @Nullable Throwable);
field public final int code;
}
@@ -205,6 +206,7 @@
}
public final class IpPrefix implements android.os.Parcelable {
+ ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
method public boolean contains(@NonNull java.net.InetAddress);
method public int describeContents();
method @NonNull public java.net.InetAddress getAddress();
@@ -292,6 +294,7 @@
ctor public NetworkCapabilities(android.net.NetworkCapabilities);
method public int describeContents();
method @NonNull public int[] getCapabilities();
+ method @NonNull public int[] getEnterpriseIds();
method public int getLinkDownstreamBandwidthKbps();
method public int getLinkUpstreamBandwidthKbps();
method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
@@ -299,6 +302,7 @@
method public int getSignalStrength();
method @Nullable public android.net.TransportInfo getTransportInfo();
method public boolean hasCapability(int);
+ method public boolean hasEnterpriseId(int);
method public boolean hasTransport(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR;
@@ -315,12 +319,15 @@
field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc
field public static final int NET_CAPABILITY_MCX = 23; // 0x17
field public static final int NET_CAPABILITY_MMS = 0; // 0x0
+ field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21
field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14
field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12
field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15
field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
+ field public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35; // 0x23
+ field public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34; // 0x22
field public static final int NET_CAPABILITY_RCS = 8; // 0x8
field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
@@ -328,6 +335,11 @@
field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
field public static final int NET_CAPABILITY_XCAP = 9; // 0x9
+ field public static final int NET_ENTERPRISE_ID_1 = 1; // 0x1
+ field public static final int NET_ENTERPRISE_ID_2 = 2; // 0x2
+ field public static final int NET_ENTERPRISE_ID_3 = 3; // 0x3
+ field public static final int NET_ENTERPRISE_ID_4 = 4; // 0x4
+ field public static final int NET_ENTERPRISE_ID_5 = 5; // 0x5
field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000
field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
field public static final int TRANSPORT_CELLULAR = 0; // 0x0
@@ -438,11 +450,15 @@
method @NonNull public android.net.IpPrefix getDestination();
method @Nullable public java.net.InetAddress getGateway();
method @Nullable public String getInterface();
+ method public int getType();
method public boolean hasGateway();
method public boolean isDefaultRoute();
method public boolean matches(java.net.InetAddress);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR;
+ field public static final int RTN_THROW = 9; // 0x9
+ field public static final int RTN_UNICAST = 1; // 0x1
+ field public static final int RTN_UNREACHABLE = 7; // 0x7
}
public abstract class SocketKeepalive implements java.lang.AutoCloseable {
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 7fc0382..8da421d 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -12,18 +12,25 @@
method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void replaceFirewallChain(int, @NonNull int[]);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setFirewallChainEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean);
- method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreferences(@NonNull android.os.UserHandle, @NonNull java.util.List<android.net.ProfileNetworkPreference>, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
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 @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void swapActiveStatsMap();
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 updateFirewallRule(int, int, boolean);
+ 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";
@@ -38,8 +45,13 @@
field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
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_POWERSAVE = 3; // 0x3
+ field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
+ field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0
field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
+ field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2; // 0x2
}
public static class ConnectivityManager.NetworkCallback {
@@ -99,6 +111,15 @@
field public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; // 0x3
}
+ public final class DhcpOption implements android.os.Parcelable {
+ ctor public DhcpOption(byte, @Nullable byte[]);
+ method public int describeContents();
+ method public byte getType();
+ method @Nullable public byte[] getValue();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpOption> CREATOR;
+ }
+
public final class NetworkAgentConfig implements android.os.Parcelable {
method @Nullable public String getSubscriberId();
method public boolean isBypassableVpn();
@@ -106,6 +127,7 @@
public static final class NetworkAgentConfig.Builder {
method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setExcludeLocalRoutesVpn(boolean);
method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
}
@@ -125,7 +147,9 @@
}
public class NetworkRequest implements android.os.Parcelable {
+ method @NonNull public int[] getEnterpriseIds();
method @NonNull public int[] getForbiddenCapabilities();
+ method public boolean hasEnterpriseId(int);
method public boolean hasForbiddenCapability(int);
}
@@ -135,6 +159,25 @@
method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
}
+ public final class ProfileNetworkPreference implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.Integer> getExcludedUids();
+ method @NonNull public java.util.List<java.lang.Integer> getIncludedUids();
+ method public int getPreference();
+ method public int getPreferenceEnterpriseId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.ProfileNetworkPreference> CREATOR;
+ }
+
+ public static final class ProfileNetworkPreference.Builder {
+ ctor public ProfileNetworkPreference.Builder();
+ method @NonNull public android.net.ProfileNetworkPreference build();
+ method @NonNull public android.net.ProfileNetworkPreference.Builder setExcludedUids(@Nullable java.util.List<java.lang.Integer>);
+ method @NonNull public android.net.ProfileNetworkPreference.Builder setIncludedUids(@Nullable java.util.List<java.lang.Integer>);
+ method @NonNull public android.net.ProfileNetworkPreference.Builder setPreference(int);
+ method @NonNull public android.net.ProfileNetworkPreference.Builder setPreferenceEnterpriseId(int);
+ }
+
public final class TestNetworkInterface implements android.os.Parcelable {
ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String);
method public int describeContents();
diff --git a/framework/api/module-lib-lint-baseline.txt b/framework/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..c7b0db5
--- /dev/null
+++ b/framework/api/module-lib-lint-baseline.txt
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+NoByteOrShort: android.net.DhcpOption#DhcpOption(byte, byte[]) parameter #0:
+ Should avoid odd sized primitives; use `int` instead of `byte` in parameter type in android.net.DhcpOption(byte type, byte[] value)
+NoByteOrShort: android.net.DhcpOption#describeContents():
+ Should avoid odd sized primitives; use `int` instead of `byte` in method android.net.DhcpOption.describeContents()
+NoByteOrShort: android.net.DhcpOption#getType():
+ Should avoid odd sized primitives; use `int` instead of `byte` in method android.net.DhcpOption.getType()
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index cfab872..b4b3588 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -131,7 +131,6 @@
}
public final class IpPrefix implements android.os.Parcelable {
- ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
ctor public IpPrefix(@NonNull String);
}
@@ -295,9 +294,11 @@
ctor public NetworkCapabilities.Builder();
ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities);
method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder addEnterpriseId(int);
method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
method @NonNull public android.net.NetworkCapabilities build();
method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder removeEnterpriseId(int);
method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int);
@@ -432,10 +433,6 @@
ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int);
ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int);
method public int getMtu();
- method public int getType();
- field public static final int RTN_THROW = 9; // 0x9
- field public static final int RTN_UNICAST = 1; // 0x1
- field public static final int RTN_UNREACHABLE = 7; // 0x7
}
public abstract class SocketKeepalive implements java.lang.AutoCloseable {
diff --git a/framework/lint-baseline.xml b/framework/lint-baseline.xml
deleted file mode 100644
index 099202f..0000000
--- a/framework/lint-baseline.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.net.ParseException`"
- errorLine1=" ParseException pe = new ParseException(e.reason, e.getCause());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/DnsResolver.java"
- line="301"
- column="37"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback`"
- errorLine1=" protected class ActiveDataSubscriptionIdListener extends TelephonyCallback"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java"
- line="96"
- column="62"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener`"
- errorLine1=" implements TelephonyCallback.ActiveDataSubscriptionIdListener {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java"
- line="97"
- column="24"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#registerTelephonyCallback`"
- errorLine1=" ctx.getSystemService(TelephonyManager.class).registerTelephonyCallback("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java"
- line="126"
- column="54"/>
- </issue>
-
-</issues>
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index c21bcfa..7593482 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -16,6 +16,7 @@
package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
import static android.net.NetworkRequest.Type.LISTEN;
import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
@@ -931,6 +932,48 @@
private final IConnectivityManager mService;
/**
+ * Firewall chain for device idle (doze mode).
+ * Allowlist of apps that have network access in device idle.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_CHAIN_DOZABLE = 1;
+
+ /**
+ * Firewall chain used for app standby.
+ * Denylist of apps that do not have network access.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_CHAIN_STANDBY = 2;
+
+ /**
+ * Firewall chain used for battery saver.
+ * Allowlist of apps that have network access when battery saver is on.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_CHAIN_POWERSAVE = 3;
+
+ /**
+ * Firewall chain used for restricted networking mode.
+ * Allowlist of apps that have access in restricted networking mode.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_CHAIN_RESTRICTED = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_RESTRICTED
+ })
+ public @interface FirewallChain {}
+
+ /**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
* case of the static set/getProcessDefaultNetwork methods and from the Network class.
* TODO: Remove this after deprecating the static methods in favor of non-static methods or
@@ -1078,7 +1121,8 @@
}
/**
- * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}.
+ * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
+ * {@see #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
* Specify that the traffic for this user should by follow the default rules.
* @hide
*/
@@ -1086,7 +1130,8 @@
public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0;
/**
- * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}.
+ * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
+ * {@see #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
* Specify that the traffic for this user should by default go on a network with
* {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network
* if no such network is available.
@@ -1095,13 +1140,25 @@
@SystemApi(client = MODULE_LIBRARIES)
public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1;
+ /**
+ * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
+ * {@see #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
+ * Specify that the traffic for this user should by default go on a network with
+ * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE} and if no such network is available
+ * should not go on the system default network
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
PROFILE_NETWORK_PREFERENCE_DEFAULT,
- PROFILE_NETWORK_PREFERENCE_ENTERPRISE
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK
})
- public @interface ProfileNetworkPreference {
+ public @interface ProfileNetworkPreferencePolicy {
}
/**
@@ -5461,6 +5518,8 @@
* @param listener an optional listener to listen for completion of the operation.
* @throws IllegalArgumentException if {@code profile} is not a valid user profile.
* @throws SecurityException if missing the appropriate permissions.
+ * @deprecated Use {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
+ * instead as it provides a more flexible API with more options.
* @hide
*/
// This function is for establishing per-profile default networking and can only be called by
@@ -5470,8 +5529,48 @@
@SuppressLint({"UserHandle"})
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ @Deprecated
public void setProfileNetworkPreference(@NonNull final UserHandle profile,
- @ProfileNetworkPreference final int preference,
+ @ProfileNetworkPreferencePolicy final int preference,
+ @Nullable @CallbackExecutor final Executor executor,
+ @Nullable final Runnable listener) {
+
+ ProfileNetworkPreference.Builder preferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ preferenceBuilder.setPreference(preference);
+ if (preference != PROFILE_NETWORK_PREFERENCE_DEFAULT) {
+ preferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ }
+ setProfileNetworkPreferences(profile,
+ List.of(preferenceBuilder.build()), executor, listener);
+ }
+
+ /**
+ * Set a list of default network selection policies for a user profile.
+ *
+ * Calling this API with a user handle defines the entire policy for that user handle.
+ * It will overwrite any setting previously set for the same user profile,
+ * and not affect previously set settings for other handles.
+ *
+ * Call this API with an empty list to remove settings for this user profile.
+ *
+ * See {@link ProfileNetworkPreference} for more details on each preference
+ * parameter.
+ *
+ * @param profile the user profile for which the preference is being set.
+ * @param profileNetworkPreferences the list of profile network preferences for the
+ * provided profile.
+ * @param executor an executor to execute the listener on. Optional if listener is null.
+ * @param listener an optional listener to listen for completion of the operation.
+ * @throws IllegalArgumentException if {@code profile} is not a valid user profile.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void setProfileNetworkPreferences(
+ @NonNull final UserHandle profile,
+ @NonNull List<ProfileNetworkPreference> profileNetworkPreferences,
@Nullable @CallbackExecutor final Executor executor,
@Nullable final Runnable listener) {
if (null != listener) {
@@ -5489,7 +5588,7 @@
};
}
try {
- mService.setProfileNetworkPreference(profile, preference, proxy);
+ mService.setProfileNetworkPreferences(profile, profileNetworkPreferences, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5511,4 +5610,141 @@
public static Range<Integer> getIpSecNetIdRange() {
return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1);
}
+
+ /**
+ * Sets whether the specified UID is allowed to use data on metered networks even when
+ * background data is restricted.
+ *
+ * @param uid uid of target app
+ * @throws IllegalStateException if updating allow list failed.
+ * @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();
+ }
+ }
+
+ /**
+ * Sets whether the specified UID is prevented from using background data on metered networks.
+ * Takes precedence over {@link #updateMeteredNetworkAllowList}.
+ *
+ * @param uid uid of target app
+ * @throws IllegalStateException if updating deny list failed.
+ * @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();
+ }
+ }
+
+ /**
+ * Sets a firewall rule for the specified UID on the specified chain.
+ *
+ * @param chain target chain.
+ * @param uid uid to allow/deny.
+ * @param allow whether networking is allowed or denied.
+ * @throws IllegalStateException if updating firewall rule failed.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void updateFirewallRule(@FirewallChain final int chain, final int uid,
+ final boolean allow) {
+ try {
+ mService.updateFirewallRule(chain, uid, allow);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enables or disables the specified firewall chain.
+ *
+ * @param chain target chain.
+ * @param enable whether the chain should be enabled.
+ * @throws IllegalStateException if enabling or disabling the firewall chain failed.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void setFirewallChainEnabled(@FirewallChain final int chain, final boolean enable) {
+ try {
+ mService.setFirewallChainEnabled(chain, enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Replaces the contents of the specified UID-based firewall chain.
+ *
+ * @param chain target chain to replace.
+ * @param uids The list of UIDs to be placed into chain.
+ * @throws IllegalStateException if replacing the firewall chain failed.
+ * @throws IllegalArgumentException if {@code chain} is not a valid chain.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void replaceFirewallChain(@FirewallChain final int chain, @NonNull final int[] uids) {
+ Objects.requireNonNull(uids);
+ try {
+ mService.replaceFirewallChain(chain, uids);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request to change the current active network stats map.
+ * STOPSHIP: Remove this API before T sdk finalized, this API is temporary added for the
+ * NetworkStatsFactory which is platform code but will be moved into connectivity (tethering)
+ * mainline module.
+ *
+ * @throws IllegalStateException if swapping active stats map failed.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void swapActiveStatsMap() {
+ try {
+ mService.swapActiveStatsMap();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/framework/src/android/net/DhcpOption.java b/framework/src/android/net/DhcpOption.java
new file mode 100644
index 0000000..a125290
--- /dev/null
+++ b/framework/src/android/net/DhcpOption.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class representing an option in the DHCP protocol.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class DhcpOption implements Parcelable {
+ private final byte mType;
+ private final byte[] mValue;
+
+ /**
+ * Constructs a DhcpOption object.
+ *
+ * @param type the type of this option
+ * @param value the value of this option. If {@code null}, DHCP packets containing this option
+ * will include the option type in the Parameter Request List. Otherwise, DHCP
+ * packets containing this option will include the option in the options section.
+ */
+ public DhcpOption(byte type, @Nullable byte[] value) {
+ mType = type;
+ mValue = value;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeByte(mType);
+ dest.writeByteArray(mValue);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @NonNull Creator<DhcpOption> CREATOR =
+ new Creator<DhcpOption>() {
+ public DhcpOption createFromParcel(Parcel in) {
+ return new DhcpOption(in.readByte(), in.createByteArray());
+ }
+
+ public DhcpOption[] newArray(int size) {
+ return new DhcpOption[size];
+ }
+ };
+
+ /** Get the type of DHCP option */
+ public byte getType() {
+ return mType;
+ }
+
+ /** Get the value of DHCP option */
+ @Nullable public byte[] getValue() {
+ return mValue == null ? null : mValue.clone();
+ }
+}
diff --git a/framework/src/android/net/DnsResolver.java b/framework/src/android/net/DnsResolver.java
index dac88ad..164160f 100644
--- a/framework/src/android/net/DnsResolver.java
+++ b/framework/src/android/net/DnsResolver.java
@@ -164,7 +164,7 @@
*/
@DnsError public final int code;
- DnsException(@DnsError int code, @Nullable Throwable cause) {
+ public DnsException(@DnsError int code, @Nullable Throwable cause) {
super(cause);
this.code = code;
}
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 50ec781..df4663f 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -36,6 +36,7 @@
import android.net.NetworkState;
import android.net.NetworkStateSnapshot;
import android.net.OemNetworkPreferences;
+import android.net.ProfileNetworkPreference;
import android.net.ProxyInfo;
import android.net.UidRange;
import android.net.QosSocketInfo;
@@ -218,7 +219,8 @@
void setOemNetworkPreference(in OemNetworkPreferences preference,
in IOnCompleteListener listener);
- void setProfileNetworkPreference(in UserHandle profile, int preference,
+ void setProfileNetworkPreferences(in UserHandle profile,
+ in List<ProfileNetworkPreference> preferences,
in IOnCompleteListener listener);
int getRestrictBackgroundStatusByCaller();
@@ -228,4 +230,16 @@
void unofferNetwork(in INetworkOfferCallback callback);
void setTestAllowBadWifiUntil(long timeMs);
+
+ void updateMeteredNetworkAllowList(int uid, boolean add);
+
+ void updateMeteredNetworkDenyList(int uid, boolean add);
+
+ void updateFirewallRule(int chain, int uid, boolean allow);
+
+ void setFirewallChainEnabled(int chain, boolean enable);
+
+ void replaceFirewallChain(int chain, in int[] uids);
+
+ void swapActiveStatsMap();
}
diff --git a/framework/src/android/net/IpPrefix.java b/framework/src/android/net/IpPrefix.java
index bf4481a..c26a0b5 100644
--- a/framework/src/android/net/IpPrefix.java
+++ b/framework/src/android/net/IpPrefix.java
@@ -87,9 +87,7 @@
*
* @param address the IP address. Must be non-null.
* @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
- * @hide
*/
- @SystemApi
public IpPrefix(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength) {
// We don't reuse the (byte[], int) constructor because it calls clone() on the byte array,
// which is unnecessary because getAddress() already returns a clone.
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index ad8396b..040bf31 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -232,6 +232,20 @@
return mLegacyExtraInfo;
}
+ /**
+ * If the {@link Network} is a VPN, whether the local traffic is exempted from the VPN.
+ * @hide
+ */
+ public boolean excludeLocalRouteVpn = false;
+
+ /**
+ * @return whether local traffic is excluded from the VPN network.
+ * @hide
+ */
+ public boolean getExcludeLocalRouteVpn() {
+ return excludeLocalRouteVpn;
+ }
+
/** @hide */
public NetworkAgentConfig() {
}
@@ -251,6 +265,7 @@
legacySubType = nac.legacySubType;
legacySubTypeName = nac.legacySubTypeName;
mLegacyExtraInfo = nac.mLegacyExtraInfo;
+ excludeLocalRouteVpn = nac.excludeLocalRouteVpn;
}
}
@@ -407,6 +422,19 @@
}
/**
+ * Sets whether the local traffic is exempted from VPN.
+ *
+ * @return this builder, to facilitate chaining.
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = MODULE_LIBRARIES)
+ public Builder setExcludeLocalRoutesVpn(boolean excludeLocalRoutes) {
+ mConfig.excludeLocalRouteVpn = excludeLocalRoutes;
+ return this;
+ }
+
+ /**
* Returns the constructed {@link NetworkAgentConfig} object.
*/
@NonNull
@@ -429,14 +457,15 @@
&& legacyType == that.legacyType
&& Objects.equals(subscriberId, that.subscriberId)
&& Objects.equals(legacyTypeName, that.legacyTypeName)
- && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo);
+ && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo)
+ && excludeLocalRouteVpn == that.excludeLocalRouteVpn;
}
@Override
public int hashCode() {
return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated,
acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId,
- skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo);
+ skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo, excludeLocalRouteVpn);
}
@Override
@@ -453,6 +482,7 @@
+ ", hasShownBroken = " + hasShownBroken
+ ", legacyTypeName = '" + legacyTypeName + '\''
+ ", legacyExtraInfo = '" + mLegacyExtraInfo + '\''
+ + ", excludeLocalRouteVpn = '" + excludeLocalRouteVpn + '\''
+ "}";
}
@@ -475,6 +505,7 @@
out.writeInt(legacySubType);
out.writeString(legacySubTypeName);
out.writeString(mLegacyExtraInfo);
+ out.writeInt(excludeLocalRouteVpn ? 1 : 0);
}
public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
@@ -494,6 +525,7 @@
networkAgentConfig.legacySubType = in.readInt();
networkAgentConfig.legacySubTypeName = in.readString();
networkAgentConfig.mLegacyExtraInfo = in.readString();
+ networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0;
return networkAgentConfig;
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 75f0129..4ae3a06 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -146,6 +146,86 @@
*/
private String mRequestorPackageName;
+ /**
+ * Enterprise capability identifier 1.
+ */
+ public static final int NET_ENTERPRISE_ID_1 = 1;
+
+ /**
+ * Enterprise capability identifier 2.
+ */
+ public static final int NET_ENTERPRISE_ID_2 = 2;
+
+ /**
+ * Enterprise capability identifier 3.
+ */
+ public static final int NET_ENTERPRISE_ID_3 = 3;
+
+ /**
+ * Enterprise capability identifier 4.
+ */
+ public static final int NET_ENTERPRISE_ID_4 = 4;
+
+ /**
+ * Enterprise capability identifier 5.
+ */
+ public static final int NET_ENTERPRISE_ID_5 = 5;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
+ NET_ENTERPRISE_ID_1,
+ NET_ENTERPRISE_ID_2,
+ NET_ENTERPRISE_ID_3,
+ NET_ENTERPRISE_ID_4,
+ NET_ENTERPRISE_ID_5,
+ })
+
+ public @interface EnterpriseId {
+ }
+
+ /**
+ * Bitfield representing the network's enterprise capability identifier. If any are specified
+ * they will be satisfied by any Network that matches all of them.
+ * {@see addEnterpriseId} for details on how masks are added
+ */
+ private int mEnterpriseId;
+
+ /**
+ * Get enteprise identifiers set.
+ *
+ * Get all the enterprise capabilities identifier set on this {@code NetworkCapability}
+ * If NET_CAPABILITY_ENTERPRISE is set and no enterprise ID is set, it is
+ * considered to have NET_CAPABILITY_ENTERPRISE by default.
+ * @return all the enterprise capabilities identifier set.
+ *
+ */
+ public @NonNull @EnterpriseId int[] getEnterpriseIds() {
+ if (hasCapability(NET_CAPABILITY_ENTERPRISE) && mEnterpriseId == 0) {
+ return new int[]{NET_ENTERPRISE_ID_1};
+ }
+ return NetworkCapabilitiesUtils.unpackBits(mEnterpriseId);
+ }
+
+ /**
+ * Tests for the presence of an enterprise capability identifier on this instance.
+ *
+ * If NET_CAPABILITY_ENTERPRISE is set and no enterprise ID is set, it is
+ * considered to have NET_CAPABILITY_ENTERPRISE by default.
+ * @param enterpriseId the enterprise capability identifier to be tested for.
+ * @return {@code true} if set on this instance.
+ */
+ public boolean hasEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ if (enterpriseId == NET_ENTERPRISE_ID_1) {
+ if (hasCapability(NET_CAPABILITY_ENTERPRISE) && mEnterpriseId == 0) {
+ return true;
+ }
+ }
+ return isValidEnterpriseId(enterpriseId)
+ && ((mEnterpriseId & (1L << enterpriseId)) != 0);
+ }
+
public NetworkCapabilities() {
clearAll();
mNetworkCapabilities = DEFAULT_CAPABILITIES;
@@ -192,6 +272,7 @@
mRequestorPackageName = null;
mSubIds = new ArraySet<>();
mUnderlyingNetworks = null;
+ mEnterpriseId = 0;
}
/**
@@ -224,6 +305,7 @@
// mUnderlyingNetworks is an unmodifiable list if non-null, so a defensive copy is not
// necessary.
mUnderlyingNetworks = nc.mUnderlyingNetworks;
+ mEnterpriseId = nc.mEnterpriseId;
}
/**
@@ -274,6 +356,9 @@
NET_CAPABILITY_VSIM,
NET_CAPABILITY_BIP,
NET_CAPABILITY_HEAD_UNIT,
+ NET_CAPABILITY_MMTEL,
+ NET_CAPABILITY_PRIORITIZE_LATENCY,
+ NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
})
public @interface NetCapability { }
@@ -512,8 +597,23 @@
*/
public static final int NET_CAPABILITY_HEAD_UNIT = 32;
+ /**
+ * Indicates that this network has ability to support MMTEL (Multimedia Telephony service).
+ */
+ public static final int NET_CAPABILITY_MMTEL = 33;
+
+ /**
+ * Indicates that this network should be able to prioritize latency for the internet.
+ */
+ public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34;
+
+ /**
+ * Indicates that this network should be able to prioritize bandwidth for the internet.
+ */
+ public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_HEAD_UNIT;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -710,6 +810,38 @@
}
/**
+ * Adds the given enterprise capability identifier to this {@code NetworkCapability} instance.
+ * Note that when searching for a network to satisfy a request, all capabilities identifier
+ * requested must be satisfied.
+ *
+ * @param enterpriseId the enterprise capability identifier to be added.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities addEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ checkValidEnterpriseId(enterpriseId);
+ mEnterpriseId |= 1 << enterpriseId;
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given enterprise capability identifier from this
+ * {@code NetworkCapability} instance that were added via addEnterpriseId(int)
+ *
+ * @param enterpriseId the enterprise capability identifier to be removed.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ private @NonNull NetworkCapabilities removeEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ checkValidEnterpriseId(enterpriseId);
+ final int mask = ~(1 << enterpriseId);
+ mEnterpriseId &= mask;
+ return this;
+ }
+
+ /**
* Set the underlying networks of this network.
*
* @param networks The underlying networks of this network.
@@ -787,18 +919,6 @@
}
}
- private void combineNetCapabilities(@NonNull NetworkCapabilities nc) {
- final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities;
- final long forbiddenCaps =
- this.mForbiddenNetworkCapabilities | nc.mForbiddenNetworkCapabilities;
- if ((wantedCaps & forbiddenCaps) != 0) {
- throw new IllegalArgumentException(
- "Cannot have the same capability in wanted and forbidden lists.");
- }
- this.mNetworkCapabilities = wantedCaps;
- this.mForbiddenNetworkCapabilities = forbiddenCaps;
- }
-
/**
* Convenience function that returns a human-readable description of the first mutable
* capability we find. Used to present an error message to apps that request mutable
@@ -821,6 +941,25 @@
return null;
}
+ private boolean equalsEnterpriseCapabilitiesId(@NonNull NetworkCapabilities nc) {
+ return nc.mEnterpriseId == this.mEnterpriseId;
+ }
+
+ private boolean satisfiedByEnterpriseCapabilitiesId(@NonNull NetworkCapabilities nc) {
+ final int requestedEnterpriseCapabilitiesId = mEnterpriseId;
+ final int providedEnterpriseCapabailitiesId = nc.mEnterpriseId;
+
+ if ((providedEnterpriseCapabailitiesId & requestedEnterpriseCapabilitiesId)
+ == requestedEnterpriseCapabilitiesId) {
+ return true;
+ } else if (providedEnterpriseCapabailitiesId == 0
+ && (requestedEnterpriseCapabilitiesId == (1L << NET_ENTERPRISE_ID_1))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
private boolean satisfiedByNetCapabilities(@NonNull NetworkCapabilities nc,
boolean onlyImmutable) {
long requestedCapabilities = mNetworkCapabilities;
@@ -1109,10 +1248,6 @@
return mTransportTypes == (1 << transportType);
}
- private void combineTransportTypes(NetworkCapabilities nc) {
- this.mTransportTypes |= nc.mTransportTypes;
- }
-
private boolean satisfiedByTransportTypes(NetworkCapabilities nc) {
return ((this.mTransportTypes == 0)
|| ((this.mTransportTypes & nc.mTransportTypes) != 0));
@@ -1293,26 +1428,6 @@
}
/**
- * Combine the administrator UIDs of the capabilities.
- *
- * <p>This is only legal if either of the administrators lists are empty, or if they are equal.
- * Combining administrator UIDs is only possible for combining non-overlapping sets of UIDs.
- *
- * <p>If both administrator lists are non-empty but not equal, they conflict with each other. In
- * this case, it would not make sense to add them together.
- */
- private void combineAdministratorUids(@NonNull final NetworkCapabilities nc) {
- if (nc.mAdministratorUids.length == 0) return;
- if (mAdministratorUids.length == 0) {
- mAdministratorUids = Arrays.copyOf(nc.mAdministratorUids, nc.mAdministratorUids.length);
- return;
- }
- if (!equalsAdministratorUids(nc)) {
- throw new IllegalStateException("Can't combine two different administrator UID lists");
- }
- }
-
- /**
* Value indicating that link bandwidth is unspecified.
* @hide
*/
@@ -1374,12 +1489,6 @@
return mLinkDownBandwidthKbps;
}
- private void combineLinkBandwidths(NetworkCapabilities nc) {
- this.mLinkUpBandwidthKbps =
- Math.max(this.mLinkUpBandwidthKbps, nc.mLinkUpBandwidthKbps);
- this.mLinkDownBandwidthKbps =
- Math.max(this.mLinkDownBandwidthKbps, nc.mLinkDownBandwidthKbps);
- }
private boolean satisfiedByLinkBandwidths(NetworkCapabilities nc) {
return !(this.mLinkUpBandwidthKbps > nc.mLinkUpBandwidthKbps
|| this.mLinkDownBandwidthKbps > nc.mLinkDownBandwidthKbps);
@@ -1466,13 +1575,6 @@
return mTransportInfo;
}
- private void combineSpecifiers(NetworkCapabilities nc) {
- if (mNetworkSpecifier != null && !mNetworkSpecifier.equals(nc.mNetworkSpecifier)) {
- throw new IllegalStateException("Can't combine two networkSpecifiers");
- }
- setNetworkSpecifier(nc.mNetworkSpecifier);
- }
-
private boolean satisfiedBySpecifier(NetworkCapabilities nc) {
return mNetworkSpecifier == null || mNetworkSpecifier.canBeSatisfiedBy(nc.mNetworkSpecifier)
|| nc.mNetworkSpecifier instanceof MatchAllNetworkSpecifier;
@@ -1482,13 +1584,6 @@
return Objects.equals(mNetworkSpecifier, nc.mNetworkSpecifier);
}
- private void combineTransportInfos(NetworkCapabilities nc) {
- if (mTransportInfo != null && !mTransportInfo.equals(nc.mTransportInfo)) {
- throw new IllegalStateException("Can't combine two TransportInfos");
- }
- setTransportInfo(nc.mTransportInfo);
- }
-
private boolean equalsTransportInfo(NetworkCapabilities nc) {
return Objects.equals(mTransportInfo, nc.mTransportInfo);
}
@@ -1543,10 +1638,6 @@
return mSignalStrength;
}
- private void combineSignalStrength(NetworkCapabilities nc) {
- this.mSignalStrength = Math.max(this.mSignalStrength, nc.mSignalStrength);
- }
-
private boolean satisfiedBySignalStrength(NetworkCapabilities nc) {
return this.mSignalStrength <= nc.mSignalStrength;
}
@@ -1652,28 +1743,6 @@
}
/**
- * Compare if the given NetworkCapabilities have the same UIDs.
- *
- * @hide
- */
- public static boolean hasSameUids(@Nullable NetworkCapabilities nc1,
- @Nullable NetworkCapabilities nc2) {
- final Set<UidRange> uids1 = (nc1 == null) ? null : nc1.mUids;
- final Set<UidRange> uids2 = (nc2 == null) ? null : nc2.mUids;
- if (null == uids1) return null == uids2;
- if (null == uids2) return false;
- // Make a copy so it can be mutated to check that all ranges in uids2 also are in uids.
- final Set<UidRange> uids = new ArraySet<>(uids2);
- for (UidRange range : uids1) {
- if (!uids.contains(range)) {
- return false;
- }
- uids.remove(range);
- }
- return uids.isEmpty();
- }
-
- /**
* Tests if the set of UIDs that this network applies to is the same as the passed network.
* <p>
* This test only checks whether equal range objects are in both sets. It will
@@ -1683,13 +1752,13 @@
* Note that this method is not very optimized, which is fine as long as it's not used very
* often.
* <p>
- * nc is assumed nonnull.
+ * nc is assumed nonnull, else NPE.
*
* @hide
*/
@VisibleForTesting
public boolean equalsUids(@NonNull NetworkCapabilities nc) {
- return hasSameUids(nc, this);
+ return UidRange.hasSameUids(nc.mUids, mUids);
}
/**
@@ -1729,7 +1798,7 @@
* @hide
*/
@VisibleForTesting
- public boolean appliesToUidRange(@Nullable UidRange requiredRange) {
+ public boolean appliesToUidRange(@NonNull UidRange requiredRange) {
if (null == mUids) return true;
for (UidRange uidRange : mUids) {
if (uidRange.containsRange(requiredRange)) {
@@ -1740,20 +1809,6 @@
}
/**
- * Combine the UIDs this network currently applies to with the UIDs the passed
- * NetworkCapabilities apply to.
- * nc is assumed nonnull.
- */
- private void combineUids(@NonNull NetworkCapabilities nc) {
- if (null == nc.mUids || null == mUids) {
- mUids = null;
- return;
- }
- mUids.addAll(nc.mUids);
- }
-
-
- /**
* The SSID of the network, or null if not applicable or unknown.
* <p>
* This is filled in by wifi code.
@@ -1796,42 +1851,6 @@
}
/**
- * Combine SSIDs of the capabilities.
- * <p>
- * This is only legal if either the SSID of this object is null, or both SSIDs are
- * equal.
- * @hide
- */
- private void combineSSIDs(@NonNull NetworkCapabilities nc) {
- if (mSSID != null && !mSSID.equals(nc.mSSID)) {
- throw new IllegalStateException("Can't combine two SSIDs");
- }
- setSSID(nc.mSSID);
- }
-
- /**
- * Combine a set of Capabilities to this one. Useful for coming up with the complete set.
- * <p>
- * Note that this method may break an invariant of having a particular capability in either
- * wanted or forbidden lists but never in both. Requests that have the same capability in
- * both lists will never be satisfied.
- * @hide
- */
- public void combineCapabilities(@NonNull NetworkCapabilities nc) {
- combineNetCapabilities(nc);
- combineTransportTypes(nc);
- combineLinkBandwidths(nc);
- combineSpecifiers(nc);
- combineTransportInfos(nc);
- combineSignalStrength(nc);
- combineUids(nc);
- combineSSIDs(nc);
- combineRequestor(nc);
- combineAdministratorUids(nc);
- combineSubscriptionIds(nc);
- }
-
- /**
* Check if our requirements are satisfied by the given {@code NetworkCapabilities}.
*
* @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements.
@@ -1846,6 +1865,7 @@
&& satisfiedByTransportTypes(nc)
&& (onlyImmutable || satisfiedByLinkBandwidths(nc))
&& satisfiedBySpecifier(nc)
+ && satisfiedByEnterpriseCapabilitiesId(nc)
&& (onlyImmutable || satisfiedBySignalStrength(nc))
&& (onlyImmutable || satisfiedByUids(nc))
&& (onlyImmutable || satisfiedBySSID(nc))
@@ -1948,7 +1968,8 @@
&& equalsRequestor(that)
&& equalsAdministratorUids(that)
&& equalsSubscriptionIds(that)
- && equalsUnderlyingNetworks(that);
+ && equalsUnderlyingNetworks(that)
+ && equalsEnterpriseCapabilitiesId(that);
}
@Override
@@ -1972,7 +1993,8 @@
+ Objects.hashCode(mRequestorPackageName) * 59
+ Arrays.hashCode(mAdministratorUids) * 61
+ Objects.hashCode(mSubIds) * 67
- + Objects.hashCode(mUnderlyingNetworks) * 71;
+ + Objects.hashCode(mUnderlyingNetworks) * 71
+ + mEnterpriseId * 73;
}
@Override
@@ -2008,6 +2030,7 @@
dest.writeString(mRequestorPackageName);
dest.writeIntArray(CollectionUtils.toIntArray(mSubIds));
dest.writeTypedList(mUnderlyingNetworks);
+ dest.writeInt(mEnterpriseId);
}
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
@@ -2037,6 +2060,7 @@
netCap.mSubIds.add(subIdInts[i]);
}
netCap.setUnderlyingNetworks(in.createTypedArrayList(Network.CREATOR));
+ netCap.mEnterpriseId = in.readInt();
return netCap;
}
@Override
@@ -2128,6 +2152,12 @@
sb.append(" SubscriptionIds: ").append(mSubIds);
}
+ if (0 != mEnterpriseId) {
+ sb.append(" EnterpriseId: ");
+ appendStringRepresentationOfBitMaskToStringBuilder(sb, mEnterpriseId,
+ NetworkCapabilities::enterpriseIdNameOf, "&");
+ }
+
sb.append(" UnderlyingNetworks: ");
if (mUnderlyingNetworks != null) {
sb.append("[");
@@ -2222,10 +2252,18 @@
case NET_CAPABILITY_VSIM: return "VSIM";
case NET_CAPABILITY_BIP: return "BIP";
case NET_CAPABILITY_HEAD_UNIT: return "HEAD_UNIT";
+ case NET_CAPABILITY_MMTEL: return "MMTEL";
+ case NET_CAPABILITY_PRIORITIZE_LATENCY: return "PRIORITIZE_LATENCY";
+ case NET_CAPABILITY_PRIORITIZE_BANDWIDTH: return "PRIORITIZE_BANDWIDTH";
default: return Integer.toString(capability);
}
}
+ private static @NonNull String enterpriseIdNameOf(
+ @NetCapability int capability) {
+ return Integer.toString(capability);
+ }
+
/**
* @hide
*/
@@ -2266,6 +2304,20 @@
}
}
+ private static boolean isValidEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int enterpriseId) {
+ return enterpriseId >= NET_ENTERPRISE_ID_1
+ && enterpriseId <= NET_ENTERPRISE_ID_5;
+ }
+
+ private static void checkValidEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int enterpriseId) {
+ if (!isValidEnterpriseId(enterpriseId)) {
+ throw new IllegalArgumentException("enterprise capability identifier "
+ + enterpriseId + " is out of range");
+ }
+ }
+
/**
* Check if this {@code NetworkCapability} instance is metered.
*
@@ -2406,25 +2458,6 @@
return TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName);
}
- /**
- * Combine requestor info of the capabilities.
- * <p>
- * This is only legal if either the requestor info of this object is reset, or both info are
- * equal.
- * nc is assumed nonnull.
- */
- private void combineRequestor(@NonNull NetworkCapabilities nc) {
- if (mRequestorUid != Process.INVALID_UID && mRequestorUid != nc.mOwnerUid) {
- throw new IllegalStateException("Can't combine two uids");
- }
- if (mRequestorPackageName != null
- && !mRequestorPackageName.equals(nc.mRequestorPackageName)) {
- throw new IllegalStateException("Can't combine two package names");
- }
- setRequestorUid(nc.mRequestorUid);
- setRequestorPackageName(nc.mRequestorPackageName);
- }
-
private boolean equalsRequestor(NetworkCapabilities nc) {
return mRequestorUid == nc.mRequestorUid
&& TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName);
@@ -2484,20 +2517,6 @@
}
/**
- * Combine subscription ID set of the capabilities.
- *
- * <p>This is only legal if the subscription Ids are equal.
- *
- * <p>If both subscription IDs are not equal, they belong to different subscription
- * (or no subscription). In this case, it would not make sense to add them together.
- */
- private void combineSubscriptionIds(@NonNull NetworkCapabilities nc) {
- if (!Objects.equals(mSubIds, nc.mSubIds)) {
- throw new IllegalStateException("Can't combine two subscription ID sets");
- }
- }
-
- /**
* Returns a bitmask of all the applicable redactions (based on the permissions held by the
* receiving app) to be performed on this object.
*
@@ -2625,6 +2644,37 @@
}
/**
+ * Adds the given enterprise capability identifier.
+ * Note that when searching for a network to satisfy a request, all capabilities identifier
+ * requested must be satisfied. Enterprise capability identifier is applicable only
+ * for NET_CAPABILITY_ENTERPRISE capability
+ *
+ * @param enterpriseId enterprise capability identifier.
+ *
+ * @return this builder
+ */
+ @NonNull
+ public Builder addEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ mCaps.addEnterpriseId(enterpriseId);
+ return this;
+ }
+
+ /**
+ * Removes the given enterprise capability identifier. Enterprise capability identifier is
+ * applicable only for NET_CAPABILITY_ENTERPRISE capability
+ *
+ * @param enterpriseId the enterprise capability identifier
+ * @return this builder
+ */
+ @NonNull
+ public Builder removeEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ mCaps.removeEnterpriseId(enterpriseId);
+ return this;
+ }
+
+ /**
* Sets the owner UID.
*
* The default value is {@link Process#INVALID_UID}. Pass this value to reset.
@@ -2882,6 +2932,12 @@
+ " administrator UIDs.");
}
}
+
+ if ((mCaps.getEnterpriseIds().length != 0)
+ && !mCaps.hasCapability(NET_CAPABILITY_ENTERPRISE)) {
+ throw new IllegalStateException("Enterprise capability identifier is applicable"
+ + " only with ENTERPRISE capability.");
+ }
return new NetworkCapabilities(mCaps);
}
}
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index afc76d6..b7a6076 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -725,6 +725,33 @@
}
/**
+ * Get the enteprise identifiers.
+ *
+ * Get all the enterprise identifiers set on this {@code NetworkCapability}
+ * @return array of all the enterprise identifiers.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public @NonNull @NetworkCapabilities.EnterpriseId int[] getEnterpriseIds() {
+ // No need to make a defensive copy here as NC#getCapabilities() already returns
+ // a new array.
+ return networkCapabilities.getEnterpriseIds();
+ }
+
+ /**
+ * Tests for the presence of an enterprise identifier on this instance.
+ *
+ * @param enterpriseId the enterprise capability identifier to be tested for.
+ * @return {@code true} if set on this instance.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public boolean hasEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int enterpriseId) {
+ return networkCapabilities.hasEnterpriseId(enterpriseId);
+ }
+
+ /**
* Gets all the forbidden capabilities set on this {@code NetworkRequest} instance.
*
* @return an array of forbidden capability values for this instance.
diff --git a/framework/src/android/net/ProfileNetworkPreference.java b/framework/src/android/net/ProfileNetworkPreference.java
new file mode 100644
index 0000000..f43acce
--- /dev/null
+++ b/framework/src/android/net/ProfileNetworkPreference.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.ConnectivityManager.ProfileNetworkPreferencePolicy;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Network preferences to be set for the user profile
+ * {@link ProfileNetworkPreferencePolicy}.
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public final class ProfileNetworkPreference implements Parcelable {
+ private final @ProfileNetworkPreferencePolicy int mPreference;
+ private final @NetworkCapabilities.EnterpriseId int mPreferenceEnterpriseId;
+ private final List<Integer> mIncludedUids;
+ private final List<Integer> mExcludedUids;
+
+ private ProfileNetworkPreference(int preference, List<Integer> includedUids,
+ List<Integer> excludedUids,
+ @NetworkCapabilities.EnterpriseId int preferenceEnterpriseId) {
+ mPreference = preference;
+ mPreferenceEnterpriseId = preferenceEnterpriseId;
+ if (includedUids != null) {
+ mIncludedUids = new ArrayList<>(includedUids);
+ } else {
+ mIncludedUids = new ArrayList<>();
+ }
+
+ if (excludedUids != null) {
+ mExcludedUids = new ArrayList<>(excludedUids);
+ } else {
+ mExcludedUids = new ArrayList<>();
+ }
+ }
+
+ private ProfileNetworkPreference(Parcel in) {
+ mPreference = in.readInt();
+ mIncludedUids = in.readArrayList(Integer.class.getClassLoader());
+ mExcludedUids = in.readArrayList(Integer.class.getClassLoader());
+ mPreferenceEnterpriseId = in.readInt();
+ }
+
+ public int getPreference() {
+ return mPreference;
+ }
+
+ /**
+ * Get the list of UIDs subject to this preference.
+ *
+ * Included UIDs and Excluded UIDs can't both be non-empty.
+ * if both are empty, it means this request applies to all uids in the user profile.
+ * if included is not empty, then only included UIDs are applied.
+ * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+ * @return List of uids included for the profile preference.
+ * {@see #getExcludedUids()}
+ */
+ public @NonNull List<Integer> getIncludedUids() {
+ return new ArrayList<>(mIncludedUids);
+ }
+
+ /**
+ * Get the list of UIDS excluded from this preference.
+ *
+ * <ul>Included UIDs and Excluded UIDs can't both be non-empty.</ul>
+ * <ul>If both are empty, it means this request applies to all uids in the user profile.</ul>
+ * <ul>If included is not empty, then only included UIDs are applied.</ul>
+ * <ul>If excluded is not empty, then it is all uids in the user profile except these UIDs.</ul>
+ * @return List of uids not included for the profile preference.
+ * {@see #getIncludedUids()}
+ */
+ public @NonNull List<Integer> getExcludedUids() {
+ return new ArrayList<>(mExcludedUids);
+ }
+
+ /**
+ * Get preference enterprise identifier.
+ *
+ * Preference enterprise identifier will be used to create different network preferences
+ * within enterprise preference category.
+ * Valid values starts from PROFILE_NETWORK_PREFERENCE_ENTERPRISE_ID_1 to
+ * NetworkCapabilities.NET_ENTERPRISE_ID_5.
+ * Preference identifier is not applicable if preference is set as
+ * PROFILE_NETWORK_PREFERENCE_DEFAULT. Default value is
+ * NetworkCapabilities.NET_ENTERPRISE_ID_1.
+ * @return Preference enterprise identifier.
+ *
+ */
+ public @NetworkCapabilities.EnterpriseId int getPreferenceEnterpriseId() {
+ return mPreferenceEnterpriseId;
+ }
+
+ @Override
+ public String toString() {
+ return "ProfileNetworkPreference{"
+ + "mPreference=" + getPreference()
+ + "mIncludedUids=" + mIncludedUids.toString()
+ + "mExcludedUids=" + mExcludedUids.toString()
+ + "mPreferenceEnterpriseId=" + mPreferenceEnterpriseId
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final ProfileNetworkPreference that = (ProfileNetworkPreference) o;
+ return mPreference == that.mPreference
+ && (Objects.equals(mIncludedUids, that.mIncludedUids))
+ && (Objects.equals(mExcludedUids, that.mExcludedUids))
+ && mPreferenceEnterpriseId == that.mPreferenceEnterpriseId;
+ }
+
+ @Override
+ public int hashCode() {
+ return mPreference
+ + mPreferenceEnterpriseId * 2
+ + (Objects.hashCode(mIncludedUids) * 11)
+ + (Objects.hashCode(mExcludedUids) * 13);
+ }
+
+ /**
+ * Builder used to create {@link ProfileNetworkPreference} objects.
+ * Specify the preferred Network preference
+ */
+ public static final class Builder {
+ private @ProfileNetworkPreferencePolicy int mPreference =
+ PROFILE_NETWORK_PREFERENCE_DEFAULT;
+ private @NonNull List<Integer> mIncludedUids = new ArrayList<>();
+ private @NonNull List<Integer> mExcludedUids = new ArrayList<>();
+ private int mPreferenceEnterpriseId;
+
+ /**
+ * Constructs an empty Builder with PROFILE_NETWORK_PREFERENCE_DEFAULT profile preference
+ */
+ public Builder() {}
+
+ /**
+ * Set the profile network preference
+ * See the documentation for the individual preferences for a description of the supported
+ * behaviors. Default value is PROFILE_NETWORK_PREFERENCE_DEFAULT.
+ * @param preference the desired network preference to use
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public Builder setPreference(@ProfileNetworkPreferencePolicy int preference) {
+ mPreference = preference;
+ return this;
+ }
+
+ /**
+ * This is a list of uids for which profile perefence is set.
+ * Null would mean that this preference applies to all uids in the profile.
+ * {@see #setExcludedUids(List<Integer>)}
+ * Included UIDs and Excluded UIDs can't both be non-empty.
+ * if both are empty, it means this request applies to all uids in the user profile.
+ * if included is not empty, then only included UIDs are applied.
+ * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+ * @param uids list of uids that are included
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public Builder setIncludedUids(@Nullable List<Integer> uids) {
+ if (uids != null) {
+ mIncludedUids = new ArrayList<Integer>(uids);
+ } else {
+ mIncludedUids = new ArrayList<Integer>();
+ }
+ return this;
+ }
+
+
+ /**
+ * This is a list of uids that are excluded for the profile perefence.
+ * {@see #setIncludedUids(List<Integer>)}
+ * Included UIDs and Excluded UIDs can't both be non-empty.
+ * if both are empty, it means this request applies to all uids in the user profile.
+ * if included is not empty, then only included UIDs are applied.
+ * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+ * @param uids list of uids that are not included
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public Builder setExcludedUids(@Nullable List<Integer> uids) {
+ if (uids != null) {
+ mExcludedUids = new ArrayList<Integer>(uids);
+ } else {
+ mExcludedUids = new ArrayList<Integer>();
+ }
+ return this;
+ }
+
+ /**
+ * Check if given preference enterprise identifier is valid
+ *
+ * Valid values starts from PROFILE_NETWORK_PREFERENCE_ENTERPRISE_ID_1 to
+ * NetworkCapabilities.NET_ENTERPRISE_ID_5.
+ * @return True if valid else false
+ * @hide
+ */
+ private boolean isEnterpriseIdentifierValid(
+ @NetworkCapabilities.EnterpriseId int identifier) {
+ if ((identifier >= NET_ENTERPRISE_ID_1)
+ && (identifier <= NET_ENTERPRISE_ID_5)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns an instance of {@link ProfileNetworkPreference} created from the
+ * fields set on this builder.
+ */
+ @NonNull
+ public ProfileNetworkPreference build() {
+ if (mIncludedUids.size() > 0 && mExcludedUids.size() > 0) {
+ throw new IllegalArgumentException("Both includedUids and excludedUids "
+ + "cannot be nonempty");
+ }
+
+ if (((mPreference != PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ && (!isEnterpriseIdentifierValid(mPreferenceEnterpriseId)))
+ || ((mPreference == PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ && (mPreferenceEnterpriseId != 0))) {
+ throw new IllegalStateException("Invalid preference enterprise identifier");
+ }
+ return new ProfileNetworkPreference(mPreference, mIncludedUids,
+ mExcludedUids, mPreferenceEnterpriseId);
+ }
+
+ /**
+ * Set the preference enterprise identifier.
+ *
+ * Preference enterprise identifier will be used to create different network preferences
+ * within enterprise preference category.
+ * Valid values starts from NetworkCapabilities.NET_ENTERPRISE_ID_1 to
+ * NetworkCapabilities.NET_ENTERPRISE_ID_5.
+ * Preference identifier is not applicable if preference is set as
+ * PROFILE_NETWORK_PREFERENCE_DEFAULT. Default value is
+ * NetworkCapabilities.NET_ENTERPRISE_ID_1.
+ * @param preferenceId preference sub level
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public Builder setPreferenceEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int preferenceId) {
+ mPreferenceEnterpriseId = preferenceId;
+ return this;
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ dest.writeInt(mPreference);
+ dest.writeList(mIncludedUids);
+ dest.writeList(mExcludedUids);
+ dest.writeInt(mPreferenceEnterpriseId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<ProfileNetworkPreference> CREATOR =
+ new Creator<ProfileNetworkPreference>() {
+ @Override
+ public ProfileNetworkPreference[] newArray(int size) {
+ return new ProfileNetworkPreference[size];
+ }
+
+ @Override
+ public ProfileNetworkPreference createFromParcel(
+ @NonNull android.os.Parcel in) {
+ return new ProfileNetworkPreference(in);
+ }
+ };
+}
diff --git a/framework/src/android/net/RouteInfo.java b/framework/src/android/net/RouteInfo.java
index fad3144..df5f151 100644
--- a/framework/src/android/net/RouteInfo.java
+++ b/framework/src/android/net/RouteInfo.java
@@ -86,16 +86,26 @@
private final String mInterface;
- /** Unicast route. @hide */
- @SystemApi
+ /**
+ * Unicast route.
+ *
+ * Indicates that destination is reachable directly or via gateway.
+ **/
public static final int RTN_UNICAST = 1;
- /** Unreachable route. @hide */
- @SystemApi
+ /**
+ * Unreachable route.
+ *
+ * Indicates that destination is unreachable.
+ **/
public static final int RTN_UNREACHABLE = 7;
- /** Throw route. @hide */
- @SystemApi
+ /**
+ * Throw route.
+ *
+ * Indicates that routing information about this destination is not in this table.
+ * Routing lookup should continue in another table.
+ **/
public static final int RTN_THROW = 9;
/**
@@ -391,10 +401,7 @@
* Retrieves the type of this route.
*
* @return The type of this route; one of the {@code RTN_xxx} constants defined in this class.
- *
- * @hide
*/
- @SystemApi
@RouteType
public int getType() {
return mType;
diff --git a/framework/src/android/net/UidRange.java b/framework/src/android/net/UidRange.java
index bd33292..a1f64f2 100644
--- a/framework/src/android/net/UidRange.java
+++ b/framework/src/android/net/UidRange.java
@@ -180,4 +180,24 @@
}
return uids;
}
+
+ /**
+ * Compare if the given UID range sets have the same UIDs.
+ *
+ * @hide
+ */
+ public static boolean hasSameUids(@Nullable Set<UidRange> uids1,
+ @Nullable Set<UidRange> uids2) {
+ if (null == uids1) return null == uids2;
+ if (null == uids2) return false;
+ // Make a copy so it can be mutated to check that all ranges in uids2 also are in uids.
+ final Set<UidRange> remainingUids = new ArraySet<>(uids2);
+ for (UidRange range : uids1) {
+ if (!remainingUids.contains(range)) {
+ return false;
+ }
+ remainingUids.remove(range);
+ }
+ return remainingUids.isEmpty();
+ }
}
diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java
index 3e7cb80..c1790c9 100644
--- a/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java
@@ -20,6 +20,7 @@
import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
import android.annotation.NonNull;
+import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -29,6 +30,7 @@
import android.database.ContentObserver;
import android.net.ConnectivityResources;
import android.net.Uri;
+import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
@@ -92,8 +94,8 @@
}
}
}
-
- @VisibleForTesting
+ // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
+ @VisibleForTesting @TargetApi(Build.VERSION_CODES.S)
protected class ActiveDataSubscriptionIdListener extends TelephonyCallback
implements TelephonyCallback.ActiveDataSubscriptionIdListener {
@Override
@@ -107,6 +109,8 @@
this(ctx, handler, null);
}
+ // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
+ @TargetApi(Build.VERSION_CODES.S)
public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
mContext = ctx;
mResources = new ConnectivityResources(ctx);
diff --git a/service-t/Android.bp b/service-t/Android.bp
new file mode 100644
index 0000000..48c74c6
--- /dev/null
+++ b/service-t/Android.bp
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// This builds T+ services depending on framework-connectivity-tiramisu
+// hidden symbols separately from the S+ services, to ensure that S+
+// services cannot accidentally depend on T+ hidden symbols from
+// framework-connectivity-tiramisu.
+java_library {
+ name: "service-connectivity-tiramisu-pre-jarjar",
+ sdk_version: "system_server_current",
+ // TODO(b/210962470): Bump this to at least S, and then T.
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.java",
+ // TODO: This is necessary just for LocalLog, remove after removing NativeDaemonConnector.
+ ":framework-connectivity-shared-srcs",
+ ":services.connectivity-tiramisu-updatable-sources",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ "framework-connectivity.impl",
+ "framework-connectivity-tiramisu.impl",
+ "service-connectivity-pre-jarjar",
+ "unsupportedappusage",
+ ],
+ static_libs: [
+ "modules-utils-build",
+ "modules-utils-statemachine",
+ "net-utils-framework-common",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+ visibility: [
+ "//packages/modules/Connectivity/service",
+ "//packages/modules/Connectivity/tests:__subpackages__",
+ ],
+}
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
new file mode 100644
index 0000000..bceeefa
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2017 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_library {
+ name: "libnetworkstats",
+ vendor_available: false,
+ host_supported: false,
+ header_libs: ["bpf_connectivity_headers"],
+ srcs: [
+ "BpfNetworkStats.cpp"
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ export_include_dirs: ["include"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+ sanitize: {
+ cfi: true,
+ },
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ ],
+ min_sdk_version: "30",
+}
+
+cc_test {
+ name: "libnetworkstats_test",
+ test_suites: ["general-tests"],
+ require_root: true, // required by setrlimitForTest()
+ header_libs: ["bpf_connectivity_headers"],
+ srcs: [
+ "BpfNetworkStatsTest.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+ static_libs: ["libgmock"],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libnetworkstats",
+ "libutils",
+ ],
+}
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
new file mode 100644
index 0000000..4d605ce
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <inttypes.h>
+#include <net/if.h>
+#include <string.h>
+#include <unordered_set>
+
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include "android-base/file.h"
+#include "android-base/strings.h"
+#include "android-base/unique_fd.h"
+#include "bpf/BpfMap.h"
+#include "bpf_shared.h"
+#include "netdbpf/BpfNetworkStats.h"
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+#define LOG_TAG "BpfNetworkStats"
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+
+// The target map for stats reading should be the inactive map, which is opposite
+// from the config value.
+static constexpr char const* STATS_MAP_PATH[] = {STATS_MAP_B_PATH, STATS_MAP_A_PATH};
+
+int bpfGetUidStatsInternal(uid_t uid, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& appUidStatsMap) {
+ auto statsEntry = appUidStatsMap.readValue(uid);
+ if (statsEntry.ok()) {
+ stats->rxPackets = statsEntry.value().rxPackets;
+ stats->txPackets = statsEntry.value().txPackets;
+ stats->rxBytes = statsEntry.value().rxBytes;
+ stats->txBytes = statsEntry.value().txBytes;
+ }
+ return (statsEntry.ok() || statsEntry.error().code() == ENOENT) ? 0
+ : -statsEntry.error().code();
+}
+
+int bpfGetUidStats(uid_t uid, Stats* stats) {
+ BpfMapRO<uint32_t, StatsValue> appUidStatsMap(APP_UID_STATS_MAP_PATH);
+
+ if (!appUidStatsMap.isValid()) {
+ int ret = -errno;
+ ALOGE("Opening appUidStatsMap(%s) failed: %s", APP_UID_STATS_MAP_PATH, strerror(errno));
+ return ret;
+ }
+ return bpfGetUidStatsInternal(uid, stats, appUidStatsMap);
+}
+
+int bpfGetIfaceStatsInternal(const char* iface, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceNameMap) {
+ int64_t unknownIfaceBytesTotal = 0;
+ stats->tcpRxPackets = -1;
+ stats->tcpTxPackets = -1;
+ const auto processIfaceStats =
+ [iface, stats, &ifaceNameMap, &unknownIfaceBytesTotal](
+ const uint32_t& key,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap) -> Result<void> {
+ char ifname[IFNAMSIZ];
+ if (getIfaceNameFromMap(ifaceNameMap, ifaceStatsMap, key, ifname, key,
+ &unknownIfaceBytesTotal)) {
+ return Result<void>();
+ }
+ if (!iface || !strcmp(iface, ifname)) {
+ Result<StatsValue> statsEntry = ifaceStatsMap.readValue(key);
+ if (!statsEntry.ok()) {
+ return statsEntry.error();
+ }
+ stats->rxPackets += statsEntry.value().rxPackets;
+ stats->txPackets += statsEntry.value().txPackets;
+ stats->rxBytes += statsEntry.value().rxBytes;
+ stats->txBytes += statsEntry.value().txBytes;
+ }
+ return Result<void>();
+ };
+ auto res = ifaceStatsMap.iterate(processIfaceStats);
+ return res.ok() ? 0 : -res.error().code();
+}
+
+int bpfGetIfaceStats(const char* iface, Stats* stats) {
+ BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
+ int ret;
+ if (!ifaceStatsMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
+ return ret;
+ }
+ BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ if (!ifaceIndexNameMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
+ return ret;
+ }
+ return bpfGetIfaceStatsInternal(iface, stats, ifaceStatsMap, ifaceIndexNameMap);
+}
+
+stats_line populateStatsEntry(const StatsKey& statsKey, const StatsValue& statsEntry,
+ const char* ifname) {
+ stats_line newLine;
+ strlcpy(newLine.iface, ifname, sizeof(newLine.iface));
+ newLine.uid = (int32_t)statsKey.uid;
+ newLine.set = (int32_t)statsKey.counterSet;
+ newLine.tag = (int32_t)statsKey.tag;
+ newLine.rxPackets = statsEntry.rxPackets;
+ newLine.txPackets = statsEntry.txPackets;
+ newLine.rxBytes = statsEntry.rxBytes;
+ newLine.txBytes = statsEntry.txBytes;
+ return newLine;
+}
+
+int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
+ int64_t unknownIfaceBytesTotal = 0;
+ const auto processDetailUidStats =
+ [lines, &limitIfaces, &limitTag, &limitUid, &unknownIfaceBytesTotal, &ifaceMap](
+ const StatsKey& key,
+ const BpfMap<StatsKey, StatsValue>& statsMap) -> Result<void> {
+ char ifname[IFNAMSIZ];
+ if (getIfaceNameFromMap(ifaceMap, statsMap, key.ifaceIndex, ifname, key,
+ &unknownIfaceBytesTotal)) {
+ return Result<void>();
+ }
+ std::string ifnameStr(ifname);
+ if (limitIfaces.size() > 0 &&
+ std::find(limitIfaces.begin(), limitIfaces.end(), ifnameStr) == limitIfaces.end()) {
+ // Nothing matched; skip this line.
+ return Result<void>();
+ }
+ if (limitTag != TAG_ALL && uint32_t(limitTag) != key.tag) {
+ return Result<void>();
+ }
+ if (limitUid != UID_ALL && uint32_t(limitUid) != key.uid) {
+ return Result<void>();
+ }
+ Result<StatsValue> statsEntry = statsMap.readValue(key);
+ if (!statsEntry.ok()) {
+ return base::ResultError(statsEntry.error().message(), statsEntry.error().code());
+ }
+ lines->push_back(populateStatsEntry(key, statsEntry.value(), ifname));
+ return Result<void>();
+ };
+ Result<void> res = statsMap.iterate(processDetailUidStats);
+ if (!res.ok()) {
+ ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s",
+ strerror(res.error().code()));
+ return -res.error().code();
+ }
+
+ // Since eBPF use hash map to record stats, network stats collected from
+ // eBPF will be out of order. And the performance of findIndexHinted in
+ // NetworkStats will also be impacted.
+ //
+ // Furthermore, since the StatsKey contains iface index, the network stats
+ // reported to framework would create items with the same iface, uid, tag
+ // and set, which causes NetworkStats maps wrong item to subtract.
+ //
+ // Thus, the stats needs to be properly sorted and grouped before reported.
+ groupNetworkStats(lines);
+ return 0;
+}
+
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid) {
+ BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ if (!ifaceIndexNameMap.isValid()) {
+ int ret = -errno;
+ ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
+ return ret;
+ }
+
+ BpfMapRO<uint32_t, uint8_t> configurationMap(CONFIGURATION_MAP_PATH);
+ if (!configurationMap.isValid()) {
+ int ret = -errno;
+ ALOGE("get configuration map fd failed: %s", strerror(errno));
+ return ret;
+ }
+ auto configuration = configurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
+ if (!configuration.ok()) {
+ ALOGE("Cannot read the old configuration from map: %s",
+ configuration.error().message().c_str());
+ return -configuration.error().code();
+ }
+ const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
+ BpfMap<StatsKey, StatsValue> statsMap(statsMapPath);
+ if (!statsMap.isValid()) {
+ int ret = -errno;
+ ALOGE("get stats map fd failed: %s, path: %s", strerror(errno), statsMapPath);
+ return ret;
+ }
+
+ // It is safe to read and clear the old map now since the
+ // networkStatsFactory should call netd to swap the map in advance already.
+ int ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid, statsMap,
+ ifaceIndexNameMap);
+ if (ret) {
+ ALOGE("parse detail network stats failed: %s", strerror(errno));
+ return ret;
+ }
+
+ Result<void> res = statsMap.clear();
+ if (!res.ok()) {
+ ALOGE("Clean up current stats map failed: %s", strerror(res.error().code()));
+ return -res.error().code();
+ }
+
+ return 0;
+}
+
+int parseBpfNetworkStatsDevInternal(std::vector<stats_line>* lines,
+ const BpfMap<uint32_t, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
+ int64_t unknownIfaceBytesTotal = 0;
+ const auto processDetailIfaceStats = [lines, &unknownIfaceBytesTotal, &ifaceMap, &statsMap](
+ const uint32_t& key, const StatsValue& value,
+ const BpfMap<uint32_t, StatsValue>&) {
+ char ifname[IFNAMSIZ];
+ if (getIfaceNameFromMap(ifaceMap, statsMap, key, ifname, key, &unknownIfaceBytesTotal)) {
+ return Result<void>();
+ }
+ StatsKey fakeKey = {
+ .uid = (uint32_t)UID_ALL,
+ .tag = (uint32_t)TAG_NONE,
+ .counterSet = (uint32_t)SET_ALL,
+ };
+ lines->push_back(populateStatsEntry(fakeKey, value, ifname));
+ return Result<void>();
+ };
+ Result<void> res = statsMap.iterateWithValue(processDetailIfaceStats);
+ if (!res.ok()) {
+ ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s",
+ strerror(res.error().code()));
+ return -res.error().code();
+ }
+
+ groupNetworkStats(lines);
+ return 0;
+}
+
+int parseBpfNetworkStatsDev(std::vector<stats_line>* lines) {
+ int ret = 0;
+ BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ if (!ifaceIndexNameMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
+ return ret;
+ }
+
+ BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
+ if (!ifaceStatsMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
+ return ret;
+ }
+ return parseBpfNetworkStatsDevInternal(lines, ifaceStatsMap, ifaceIndexNameMap);
+}
+
+uint64_t combineUidTag(const uid_t uid, const uint32_t tag) {
+ return (uint64_t)uid << 32 | tag;
+}
+
+void groupNetworkStats(std::vector<stats_line>* lines) {
+ if (lines->size() <= 1) return;
+ std::sort(lines->begin(), lines->end());
+
+ // Similar to std::unique(), but aggregates the duplicates rather than discarding them.
+ size_t nextOutput = 0;
+ for (size_t i = 1; i < lines->size(); i++) {
+ if (lines->at(nextOutput) == lines->at(i)) {
+ lines->at(nextOutput) += lines->at(i);
+ } else {
+ nextOutput++;
+ if (nextOutput != i) {
+ lines->at(nextOutput) = lines->at(i);
+ }
+ }
+ }
+
+ if (lines->size() != nextOutput + 1) {
+ lines->resize(nextOutput + 1);
+ }
+}
+
+// True if lhs equals to rhs, only compare iface, uid, tag and set.
+bool operator==(const stats_line& lhs, const stats_line& rhs) {
+ return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag) && (lhs.set == rhs.set) &&
+ !strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface)));
+}
+
+// True if lhs is smaller than rhs, only compare iface, uid, tag and set.
+bool operator<(const stats_line& lhs, const stats_line& rhs) {
+ int ret = strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface));
+ if (ret != 0) return ret < 0;
+ if (lhs.uid < rhs.uid) return true;
+ if (lhs.uid > rhs.uid) return false;
+ if (lhs.tag < rhs.tag) return true;
+ if (lhs.tag > rhs.tag) return false;
+ if (lhs.set < rhs.set) return true;
+ if (lhs.set > rhs.set) return false;
+ return false;
+}
+
+stats_line& stats_line::operator=(const stats_line& rhs) {
+ if (this == &rhs) return *this;
+
+ strlcpy(iface, rhs.iface, sizeof(iface));
+ uid = rhs.uid;
+ set = rhs.set;
+ tag = rhs.tag;
+ rxPackets = rhs.rxPackets;
+ txPackets = rhs.txPackets;
+ rxBytes = rhs.rxBytes;
+ txBytes = rhs.txBytes;
+ return *this;
+}
+
+stats_line& stats_line::operator+=(const stats_line& rhs) {
+ rxPackets += rhs.rxPackets;
+ txPackets += rhs.txPackets;
+ rxBytes += rhs.rxBytes;
+ txBytes += rhs.txBytes;
+ return *this;
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
new file mode 100644
index 0000000..4974b96
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+#include "netdbpf/BpfNetworkStats.h"
+
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+using base::unique_fd;
+
+constexpr int TEST_MAP_SIZE = 10;
+constexpr uid_t TEST_UID1 = 10086;
+constexpr uid_t TEST_UID2 = 12345;
+constexpr uint32_t TEST_TAG = 42;
+constexpr int TEST_COUNTERSET0 = 0;
+constexpr int TEST_COUNTERSET1 = 1;
+constexpr uint64_t TEST_BYTES0 = 1000;
+constexpr uint64_t TEST_BYTES1 = 2000;
+constexpr uint64_t TEST_PACKET0 = 100;
+constexpr uint64_t TEST_PACKET1 = 200;
+constexpr const char IFACE_NAME1[] = "lo";
+constexpr const char IFACE_NAME2[] = "wlan0";
+constexpr const char IFACE_NAME3[] = "rmnet_data0";
+// A iface name that the size is bigger than IFNAMSIZ
+constexpr const char LONG_IFACE_NAME[] = "wlanWithALongName";
+constexpr const char TRUNCATED_IFACE_NAME[] = "wlanWithALongNa";
+constexpr uint32_t IFACE_INDEX1 = 1;
+constexpr uint32_t IFACE_INDEX2 = 2;
+constexpr uint32_t IFACE_INDEX3 = 3;
+constexpr uint32_t IFACE_INDEX4 = 4;
+constexpr uint32_t UNKNOWN_IFACE = 0;
+
+class BpfNetworkStatsHelperTest : public testing::Test {
+ protected:
+ BpfNetworkStatsHelperTest() {}
+ BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
+ BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
+ BpfMap<StatsKey, StatsValue> mFakeStatsMap;
+ BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
+ BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap;
+
+ void SetUp() {
+ ASSERT_EQ(0, setrlimitForTest());
+
+ mFakeCookieTagMap = BpfMap<uint64_t, UidTagValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeCookieTagMap.getMap());
+
+ mFakeAppUidStatsMap = BpfMap<uint32_t, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeAppUidStatsMap.getMap());
+
+ mFakeStatsMap = BpfMap<StatsKey, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeStatsMap.getMap());
+
+ mFakeIfaceIndexNameMap = BpfMap<uint32_t, IfaceValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeIfaceIndexNameMap.getMap());
+
+ mFakeIfaceStatsMap = BpfMap<uint32_t, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeIfaceStatsMap.getMap());
+ }
+
+ void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) {
+ auto tagResult = mFakeCookieTagMap.readValue(cookie);
+ EXPECT_RESULT_OK(tagResult);
+ EXPECT_EQ(uid, tagResult.value().uid);
+ EXPECT_EQ(tag, tagResult.value().tag);
+ }
+
+ void populateFakeStats(uid_t uid, uint32_t tag, uint32_t ifaceIndex, uint32_t counterSet,
+ StatsValue value, BpfMap<StatsKey, StatsValue>& map) {
+ StatsKey key = {
+ .uid = (uint32_t)uid, .tag = tag, .counterSet = counterSet, .ifaceIndex = ifaceIndex};
+ EXPECT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+ }
+
+ void updateIfaceMap(const char* ifaceName, uint32_t ifaceIndex) {
+ IfaceValue iface;
+ strlcpy(iface.name, ifaceName, IFNAMSIZ);
+ EXPECT_RESULT_OK(mFakeIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY));
+ }
+
+ void expectStatsEqual(const StatsValue& target, const Stats& result) {
+ EXPECT_EQ(target.rxPackets, result.rxPackets);
+ EXPECT_EQ(target.rxBytes, result.rxBytes);
+ EXPECT_EQ(target.txPackets, result.txPackets);
+ EXPECT_EQ(target.txBytes, result.txBytes);
+ }
+
+ void expectStatsLineEqual(const StatsValue target, const char* iface, uint32_t uid,
+ int counterSet, uint32_t tag, const stats_line& result) {
+ EXPECT_EQ(0, strcmp(iface, result.iface));
+ EXPECT_EQ(uid, (uint32_t)result.uid);
+ EXPECT_EQ((uint32_t) counterSet, result.set);
+ EXPECT_EQ(tag, (uint32_t)result.tag);
+ EXPECT_EQ(target.rxPackets, (uint64_t)result.rxPackets);
+ EXPECT_EQ(target.rxBytes, (uint64_t)result.rxBytes);
+ EXPECT_EQ(target.txPackets, (uint64_t)result.txPackets);
+ EXPECT_EQ(target.txBytes, (uint64_t)result.txBytes);
+ }
+};
+
+// TEST to verify the behavior of bpf map when cocurrent deletion happens when
+// iterating the same map.
+TEST_F(BpfNetworkStatsHelperTest, TestIterateMapWithDeletion) {
+ for (int i = 0; i < 5; i++) {
+ uint64_t cookie = i + 1;
+ UidTagValue tag = {.uid = TEST_UID1, .tag = TEST_TAG};
+ EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, tag, BPF_ANY));
+ }
+ uint64_t curCookie = 0;
+ auto nextCookie = mFakeCookieTagMap.getNextKey(curCookie);
+ EXPECT_RESULT_OK(nextCookie);
+ uint64_t headOfMap = nextCookie.value();
+ curCookie = nextCookie.value();
+ // Find the second entry in the map, then immediately delete it.
+ nextCookie = mFakeCookieTagMap.getNextKey(curCookie);
+ EXPECT_RESULT_OK(nextCookie);
+ EXPECT_RESULT_OK(mFakeCookieTagMap.deleteValue((nextCookie.value())));
+ // Find the entry that is now immediately after headOfMap, then delete that.
+ nextCookie = mFakeCookieTagMap.getNextKey(curCookie);
+ EXPECT_RESULT_OK(nextCookie);
+ EXPECT_RESULT_OK(mFakeCookieTagMap.deleteValue((nextCookie.value())));
+ // Attempting to read an entry that has been deleted fails with ENOENT.
+ curCookie = nextCookie.value();
+ auto tagResult = mFakeCookieTagMap.readValue(curCookie);
+ EXPECT_EQ(ENOENT, tagResult.error().code());
+ // Finding the entry after our deleted entry restarts iteration from the beginning of the map.
+ nextCookie = mFakeCookieTagMap.getNextKey(curCookie);
+ EXPECT_RESULT_OK(nextCookie);
+ EXPECT_EQ(headOfMap, nextCookie.value());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestBpfIterateMap) {
+ for (int i = 0; i < 5; i++) {
+ uint64_t cookie = i + 1;
+ UidTagValue tag = {.uid = TEST_UID1, .tag = TEST_TAG};
+ EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, tag, BPF_ANY));
+ }
+ int totalCount = 0;
+ int totalSum = 0;
+ const auto iterateWithoutDeletion =
+ [&totalCount, &totalSum](const uint64_t& key, const BpfMap<uint64_t, UidTagValue>&) {
+ EXPECT_GE((uint64_t)5, key);
+ totalCount++;
+ totalSum += key;
+ return Result<void>();
+ };
+ EXPECT_RESULT_OK(mFakeCookieTagMap.iterate(iterateWithoutDeletion));
+ EXPECT_EQ(5, totalCount);
+ EXPECT_EQ(1 + 2 + 3 + 4 + 5, totalSum);
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestUidStatsNoTraffic) {
+ StatsValue value1 = {
+ .rxPackets = 0,
+ .rxBytes = 0,
+ .txPackets = 0,
+ .txBytes = 0,
+ };
+ Stats result1 = {};
+ ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID1, &result1, mFakeAppUidStatsMap));
+ expectStatsEqual(value1, result1);
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetUidStatsTotal) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET0 * 2,
+ .rxBytes = TEST_BYTES0 * 2,
+ .txPackets = TEST_PACKET1 * 2,
+ .txBytes = TEST_BYTES1 * 2,
+ };
+ ASSERT_RESULT_OK(mFakeAppUidStatsMap.writeValue(TEST_UID1, value1, BPF_ANY));
+ ASSERT_RESULT_OK(mFakeAppUidStatsMap.writeValue(TEST_UID2, value2, BPF_ANY));
+ Stats result1 = {};
+ ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID1, &result1, mFakeAppUidStatsMap));
+ expectStatsEqual(value1, result1);
+
+ Stats result2 = {};
+ ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID2, &result2, mFakeAppUidStatsMap));
+ expectStatsEqual(value2, result2);
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET1, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID2, 0, IFACE_INDEX3, TEST_COUNTERSET1, value1, mFakeStatsMap);
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)2, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME3, TEST_UID2, TEST_COUNTERSET1, 0, lines.front());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsInternal) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET1,
+ .rxBytes = TEST_BYTES1,
+ .txPackets = TEST_PACKET0,
+ .txBytes = TEST_BYTES0,
+ };
+ uint32_t ifaceStatsKey = IFACE_INDEX1;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX2;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX3;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+
+ Stats result1 = {};
+ ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME1, &result1, mFakeIfaceStatsMap,
+ mFakeIfaceIndexNameMap));
+ expectStatsEqual(value1, result1);
+ Stats result2 = {};
+ ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME2, &result2, mFakeIfaceStatsMap,
+ mFakeIfaceIndexNameMap));
+ expectStatsEqual(value2, result2);
+ Stats totalResult = {};
+ ASSERT_EQ(0, bpfGetIfaceStatsInternal(NULL, &totalResult, mFakeIfaceStatsMap,
+ mFakeIfaceIndexNameMap));
+ StatsValue totalValue = {
+ .rxPackets = TEST_PACKET0 * 2 + TEST_PACKET1,
+ .rxBytes = TEST_BYTES0 * 2 + TEST_BYTES1,
+ .txPackets = TEST_PACKET1 * 2 + TEST_PACKET0,
+ .txBytes = TEST_BYTES1 * 2 + TEST_BYTES0,
+ };
+ expectStatsEqual(totalValue, totalResult);
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsDetail) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value1,
+ mFakeStatsMap);
+ populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)4, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)3, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)2, lines.size());
+ lines.clear();
+ ifaces.push_back(std::string(IFACE_NAME1));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines.front());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsWithSkippedIface) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ populateFakeStats(0, 0, 0, OVERFLOW_COUNTERSET, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET1, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID2, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)4, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)3, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, 0, lines.front());
+ lines.clear();
+ ifaces.push_back(std::string(IFACE_NAME1));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)2, lines.size());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestUnknownIfaceError) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0 * 20,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1 * 20,
+ };
+ uint32_t ifaceIndex = UNKNOWN_IFACE;
+ populateFakeStats(TEST_UID1, 0, ifaceIndex, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0 * 40,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1 * 40,
+ };
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap);
+ StatsKey curKey = {
+ .uid = TEST_UID1,
+ .tag = 0,
+ .counterSet = TEST_COUNTERSET0,
+ .ifaceIndex = ifaceIndex,
+ };
+ char ifname[IFNAMSIZ];
+ int64_t unknownIfaceBytesTotal = 0;
+ ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
+ ifname, curKey, &unknownIfaceBytesTotal));
+ ASSERT_EQ(((int64_t)(TEST_BYTES0 * 20 + TEST_BYTES1 * 20)), unknownIfaceBytesTotal);
+ curKey.ifaceIndex = IFACE_INDEX2;
+ ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
+ ifname, curKey, &unknownIfaceBytesTotal));
+ ASSERT_EQ(-1, unknownIfaceBytesTotal);
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ // TODO: find a way to test the total of unknown Iface Bytes go above limit.
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines.front());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsDetail) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
+ updateIfaceMap(LONG_IFACE_NAME, IFACE_INDEX4);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET1,
+ .rxBytes = TEST_BYTES1,
+ .txPackets = TEST_PACKET0,
+ .txBytes = TEST_BYTES0,
+ };
+ uint32_t ifaceStatsKey = IFACE_INDEX1;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX2;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX3;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX4;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ std::vector<stats_line> lines;
+ ASSERT_EQ(0,
+ parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)4, lines.size());
+
+ expectStatsLineEqual(value1, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
+ expectStatsLineEqual(value1, IFACE_NAME3, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
+ expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[2]);
+ ASSERT_EQ(0, strcmp(TRUNCATED_IFACE_NAME, lines[3].iface));
+ expectStatsLineEqual(value2, TRUNCATED_IFACE_NAME, UID_ALL, SET_ALL, TAG_NONE, lines[3]);
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
+ // Create iface indexes with duplicate iface name.
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX3); // Duplicate!
+
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET1,
+ .rxBytes = TEST_BYTES1,
+ .txPackets = TEST_PACKET0,
+ .txBytes = TEST_BYTES0,
+ };
+ StatsValue value3 = {
+ .rxPackets = TEST_PACKET0 * 2,
+ .rxBytes = TEST_BYTES0 * 2,
+ .txPackets = TEST_PACKET1 * 2,
+ .txBytes = TEST_BYTES1 * 2,
+ };
+
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+
+ // Test empty stats.
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 0, lines.size());
+ lines.clear();
+
+ // Test 1 line stats.
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+ lines.clear();
+
+ // These items should not be grouped.
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET1, value2, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2,
+ mFakeStatsMap);
+ populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 5, lines.size());
+ lines.clear();
+
+ // These items should be grouped.
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 5, lines.size());
+
+ // Verify Sorted & Grouped.
+ expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+ expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, TEST_TAG, lines[1]);
+ expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG + 1, lines[2]);
+ expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, TEST_TAG, lines[3]);
+ expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[4]);
+ lines.clear();
+
+ // Perform test on IfaceStats.
+ uint32_t ifaceStatsKey = IFACE_INDEX2;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX1;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+
+ // This should be grouped.
+ ifaceStatsKey = IFACE_INDEX3;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+
+ ASSERT_EQ(0,
+ parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 2, lines.size());
+
+ expectStatsLineEqual(value3, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
+ expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
+ lines.clear();
+}
+
+// Test to verify that subtract overflow will not be triggered by the compare function invoked from
+// sorting. See http:/b/119193941.
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortAndOverflow) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+
+ // Mutate uid, 0 < TEST_UID1 < INT_MAX < INT_MIN < UINT_MAX.
+ populateFakeStats(0, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(UINT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(INT_MIN, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(INT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+ // Mutate tag, 0 < TEST_TAG < INT_MAX < INT_MIN < UINT_MAX.
+ populateFakeStats(TEST_UID1, INT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, INT_MIN, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, UINT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+ // TODO: Mutate counterSet and enlarge TEST_MAP_SIZE if overflow on counterSet is possible.
+
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 8, lines.size());
+
+ // Uid 0 first
+ expectStatsLineEqual(value1, IFACE_NAME1, 0, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+
+ // Test uid, mutate tag.
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[1]);
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MAX, lines[2]);
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MIN, lines[3]);
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, UINT_MAX, lines[4]);
+
+ // Mutate uid.
+ expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[5]);
+ expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN, TEST_COUNTERSET0, TEST_TAG, lines[6]);
+ expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[7]);
+ lines.clear();
+}
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
new file mode 100644
index 0000000..8ab7e25
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _BPF_NETWORKSTATS_H
+#define _BPF_NETWORKSTATS_H
+
+#include <bpf/BpfMap.h>
+#include "bpf_shared.h"
+
+namespace android {
+namespace bpf {
+
+// TODO: set this to a proper value based on the map size;
+constexpr int TAG_STATS_MAP_SOFT_LIMIT = 3;
+constexpr int UID_ALL = -1;
+constexpr int TAG_ALL = -1;
+constexpr int TAG_NONE = 0;
+constexpr int SET_ALL = -1;
+constexpr int SET_DEFAULT = 0;
+constexpr int SET_FOREGROUND = 1;
+
+// The limit for stats received by a unknown interface;
+constexpr const int64_t MAX_UNKNOWN_IFACE_BYTES = 100 * 1000;
+
+// This is used by
+// frameworks/base/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+// make sure it is consistent with the JNI code before changing this.
+struct stats_line {
+ char iface[32];
+ uint32_t uid;
+ uint32_t set;
+ uint32_t tag;
+ int64_t rxBytes;
+ int64_t rxPackets;
+ int64_t txBytes;
+ int64_t txPackets;
+
+ stats_line& operator=(const stats_line& rhs);
+ stats_line& operator+=(const stats_line& rhs);
+};
+
+bool operator==(const stats_line& lhs, const stats_line& rhs);
+bool operator<(const stats_line& lhs, const stats_line& rhs);
+
+// For test only
+int bpfGetUidStatsInternal(uid_t uid, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& appUidStatsMap);
+// For test only
+int bpfGetIfaceStatsInternal(const char* iface, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceNameMap);
+// For test only
+int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap);
+// For test only
+int cleanStatsMapInternal(const base::unique_fd& cookieTagMap, const base::unique_fd& tagStatsMap);
+// For test only
+template <class Key>
+int getIfaceNameFromMap(const BpfMap<uint32_t, IfaceValue>& ifaceMap,
+ const BpfMap<Key, StatsValue>& statsMap, uint32_t ifaceIndex, char* ifname,
+ const Key& curKey, int64_t* unknownIfaceBytesTotal) {
+ auto iface = ifaceMap.readValue(ifaceIndex);
+ if (!iface.ok()) {
+ maybeLogUnknownIface(ifaceIndex, statsMap, curKey, unknownIfaceBytesTotal);
+ return -ENODEV;
+ }
+ strlcpy(ifname, iface.value().name, sizeof(IfaceValue));
+ return 0;
+}
+
+template <class Key>
+void maybeLogUnknownIface(int ifaceIndex, const BpfMap<Key, StatsValue>& statsMap,
+ const Key& curKey, int64_t* unknownIfaceBytesTotal) {
+ // Have we already logged an error?
+ if (*unknownIfaceBytesTotal == -1) {
+ return;
+ }
+
+ // Are we undercounting enough data to be worth logging?
+ auto statsEntry = statsMap.readValue(curKey);
+ if (!statsEntry.ok()) {
+ // No data is being undercounted.
+ return;
+ }
+
+ *unknownIfaceBytesTotal += (statsEntry.value().rxBytes + statsEntry.value().txBytes);
+ if (*unknownIfaceBytesTotal >= MAX_UNKNOWN_IFACE_BYTES) {
+ ALOGE("Unknown name for ifindex %d with more than %" PRId64 " bytes of traffic", ifaceIndex,
+ *unknownIfaceBytesTotal);
+ *unknownIfaceBytesTotal = -1;
+ }
+}
+
+// For test only
+int parseBpfNetworkStatsDevInternal(std::vector<stats_line>* lines,
+ const BpfMap<uint32_t, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap);
+
+int bpfGetUidStats(uid_t uid, Stats* stats);
+int bpfGetIfaceStats(const char* iface, Stats* stats);
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid);
+
+int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
+void groupNetworkStats(std::vector<stats_line>* lines);
+int cleanStatsMap();
+} // namespace bpf
+} // namespace android
+
+#endif // _BPF_NETWORKSTATS_H
diff --git a/service/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
similarity index 68%
rename from service/src/com/android/server/ConnectivityServiceInitializer.java
rename to service-t/src/com/android/server/ConnectivityServiceInitializer.java
index b1a56ae..23d8bdc 100644
--- a/service/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.util.Log;
+import com.android.modules.utils.build.SdkLevel;
+
/**
* Connectivity service initializer for core networking. This is called by system server to create
* a new instance of ConnectivityService.
@@ -26,12 +28,14 @@
public final class ConnectivityServiceInitializer extends SystemService {
private static final String TAG = ConnectivityServiceInitializer.class.getSimpleName();
private final ConnectivityService mConnectivity;
+ private final NsdService mNsdService;
public ConnectivityServiceInitializer(Context context) {
super(context);
// Load JNI libraries used by ConnectivityService and its dependencies
System.loadLibrary("service-connectivity");
mConnectivity = new ConnectivityService(context);
+ mNsdService = createNsdService(context);
}
@Override
@@ -39,5 +43,20 @@
Log.i(TAG, "Registering " + Context.CONNECTIVITY_SERVICE);
publishBinderService(Context.CONNECTIVITY_SERVICE, mConnectivity,
/* allowIsolated= */ false);
+ if (mNsdService != null) {
+ Log.i(TAG, "Registering " + Context.NSD_SERVICE);
+ publishBinderService(Context.NSD_SERVICE, mNsdService, /* allowIsolated= */ false);
+ }
+ }
+
+ /** Return NsdService instance or null if current SDK is lower than T */
+ private NsdService createNsdService(final Context context) {
+ if (!SdkLevel.isAtLeastT()) return null;
+ try {
+ return NsdService.create(context);
+ } catch (InterruptedException e) {
+ Log.d(TAG, "Unable to get NSD service", e);
+ return null;
+ }
}
}
diff --git a/service/Android.bp b/service/Android.bp
index 02717f7..306bd42 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -19,6 +19,34 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// 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.
+cc_library_shared {
+ name: "libcom_android_connectivity_com_android_net_module_util_jni",
+ min_sdk_version: "30",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "jni/com_android_net_module_util/onload.cpp",
+ ],
+ stl: "libc++_static",
+ static_libs: [
+ "libnet_utils_device_common_bpfjni",
+ "libtcutils",
+ ],
+ shared_libs: [
+ "liblog",
+ "libnativehelper",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
+
cc_library_shared {
name: "libservice-connectivity",
min_sdk_version: "30",
@@ -30,12 +58,19 @@
],
srcs: [
"jni/com_android_server_TestNetworkService.cpp",
+ "jni/com_android_server_connectivity_ClatCoordinator.cpp",
"jni/onload.cpp",
],
stl: "libc++_static",
header_libs: [
"libbase_headers",
],
+ static_libs: [
+ "libbase",
+ "libclat",
+ "libip_checksum",
+ "libnetjniutils",
+ ],
shared_libs: [
"liblog",
"libnativehelper",
@@ -69,16 +104,23 @@
"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",
"service-connectivity-protos",
+ "NetworkStackApiStableShims",
],
apex_available: [
"com.android.tethering",
],
+ lint: { strict_updatability_linting: true },
+ visibility: [
+ "//packages/modules/Connectivity/service-t",
+ "//packages/modules/Connectivity/tests:__subpackages__",
+ ],
}
java_library {
@@ -95,6 +137,7 @@
apex_available: [
"com.android.tethering",
],
+ lint: { strict_updatability_linting: true },
}
java_library {
@@ -102,13 +145,19 @@
sdk_version: "system_server_current",
min_sdk_version: "30",
installable: true,
+ // This library combines system server jars that have access to different bootclasspath jars.
+ // Lower SDK service jars must not depend on higher SDK jars as that would let them
+ // transitively depend on the wrong bootclasspath jars. Sources also cannot be added here as
+ // they would transitively depend on bootclasspath jars that may not be available.
static_libs: [
"service-connectivity-pre-jarjar",
+ "service-connectivity-tiramisu-pre-jarjar",
],
jarjar_rules: "jarjar-rules.txt",
apex_available: [
"com.android.tethering",
],
+ lint: { strict_updatability_linting: true },
}
filegroup {
@@ -116,3 +165,11 @@
srcs: ["jarjar-rules.txt"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
+
+// TODO: This filegroup temporary exposes for NetworkStats. It should be
+// removed right after NetworkStats moves into mainline module.
+filegroup {
+ name: "traffic-controller-utils",
+ srcs: ["src/com/android/server/BpfNetMaps.java"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/service/ServiceConnectivityResources/res/values-ta/strings.xml b/service/ServiceConnectivityResources/res/values-ta/strings.xml
index 43a3f41..9850a35 100644
--- a/service/ServiceConnectivityResources/res/values-ta/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ta/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"சிஸ்டம் இணைப்பு மூலங்கள்"</string>
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"சிஸ்டம் இணைப்பு தகவல்கள்"</string>
<string name="wifi_available_sign_in" msgid="8041178343789805553">"வைஃபை நெட்வொர்க்கில் உள்நுழையவும்"</string>
<string name="network_available_sign_in" msgid="2622520134876355561">"நெட்வொர்க்கில் உள்நுழையவும்"</string>
<!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index e5d1a88..f658a5e 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -90,5 +90,12 @@
# From services-connectivity-shared-srcs
rule android.net.util.NetworkConstants* com.android.connectivity.@0
+# From modules-utils-statemachine
+rule com.android.internal.util.IState* com.android.connectivity.@0
+rule com.android.internal.util.State* com.android.connectivity.@0
+
+# From the API shims
+rule com.android.networkstack.apishim.** com.android.connectivity.@0
+
# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/jni/com_android_net_module_util/onload.cpp b/service/jni/com_android_net_module_util/onload.cpp
new file mode 100644
index 0000000..07ae31c
--- /dev/null
+++ b/service/jni/com_android_net_module_util/onload.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include <log/log.h>
+
+namespace android {
+
+int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
+int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ ALOGE("GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_com_android_net_module_util_BpfMap(env,
+ "com/android/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;
+
+ return JNI_VERSION_1_6;
+}
+
+};
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
new file mode 100644
index 0000000..b445462
--- /dev/null
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -0,0 +1,527 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "jniClatCoordinator"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_tun.h>
+#include <linux/ioctl.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <net/if.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <string>
+
+#include <netjniutils/netjniutils.h>
+
+#include "libclat/bpfhelper.h"
+#include "libclat/clatutils.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+// Sync from system/netd/include/netid_client.h
+#define MARK_UNSET 0u
+
+// Sync from system/netd/server/NetdConstants.h
+#define __INT_STRLEN(i) sizeof(#i)
+#define _INT_STRLEN(i) __INT_STRLEN(i)
+#define INT32_STRLEN _INT_STRLEN(INT32_MIN)
+
+#define DEVICEPREFIX "v4-"
+
+namespace android {
+static const char* kClatdPath = "/apex/com.android.tethering/bin/for-system/clatd";
+
+static void throwIOException(JNIEnv* env, const char* msg, int error) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
+}
+
+jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv* env,
+ jobject clazz,
+ jstring v4addr,
+ jint prefixlen) {
+ ScopedUtfChars address(env, v4addr);
+ in_addr ip;
+ if (inet_pton(AF_INET, address.c_str(), &ip) != 1) {
+ throwIOException(env, "invalid address", EINVAL);
+ return nullptr;
+ }
+
+ // Pick an IPv4 address.
+ // TODO: this picks the address based on other addresses that are assigned to interfaces, but
+ // the address is only actually assigned to an interface once clatd starts up. So we could end
+ // up with two clatd instances with the same IPv4 address.
+ // Stop doing this and instead pick a free one from the kV4Addr pool.
+ in_addr v4 = {net::clat::selectIpv4Address(ip, prefixlen)};
+ if (v4.s_addr == INADDR_NONE) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "No free IPv4 address in %s/%d",
+ address.c_str(), prefixlen);
+ return nullptr;
+ }
+
+ char addrstr[INET_ADDRSTRLEN];
+ if (!inet_ntop(AF_INET, (void*)&v4, addrstr, sizeof(addrstr))) {
+ throwIOException(env, "invalid address", EADDRNOTAVAIL);
+ return nullptr;
+ }
+ return env->NewStringUTF(addrstr);
+}
+
+// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
+jstring com_android_server_connectivity_ClatCoordinator_generateIpv6Address(
+ JNIEnv* env, jobject clazz, jstring ifaceStr, jstring v4Str, jstring prefix64Str) {
+ ScopedUtfChars iface(env, ifaceStr);
+ ScopedUtfChars addr4(env, v4Str);
+ ScopedUtfChars prefix64(env, prefix64Str);
+
+ if (iface.c_str() == nullptr) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid null interface name");
+ return nullptr;
+ }
+
+ in_addr v4;
+ if (inet_pton(AF_INET, addr4.c_str(), &v4) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid clat v4 address %s",
+ addr4.c_str());
+ return nullptr;
+ }
+
+ in6_addr nat64Prefix;
+ if (inet_pton(AF_INET6, prefix64.c_str(), &nat64Prefix) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid prefix %s", prefix64.c_str());
+ return nullptr;
+ }
+
+ in6_addr v6;
+ if (net::clat::generateIpv6Address(iface.c_str(), v4, nat64Prefix, &v6)) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Unable to find global source address on %s for %s", iface.c_str(),
+ prefix64.c_str());
+ return nullptr;
+ }
+
+ char addrstr[INET6_ADDRSTRLEN];
+ if (!inet_ntop(AF_INET6, (void*)&v6, addrstr, sizeof(addrstr))) {
+ throwIOException(env, "invalid address", EADDRNOTAVAIL);
+ return nullptr;
+ }
+ return env->NewStringUTF(addrstr);
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_createTunInterface(JNIEnv* env,
+ jobject clazz,
+ jstring tuniface) {
+ ScopedUtfChars v4interface(env, tuniface);
+
+ // open the tun device in non blocking mode as required by clatd
+ jint fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC);
+ if (fd == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "open tun device failed (%s)",
+ strerror(errno));
+ return -1;
+ }
+
+ struct ifreq ifr = {
+ .ifr_flags = IFF_TUN,
+ };
+ strlcpy(ifr.ifr_name, v4interface.c_str(), sizeof(ifr.ifr_name));
+
+ if (ioctl(fd, TUNSETIFF, &ifr, sizeof(ifr))) {
+ close(fd);
+ jniThrowExceptionFmt(env, "java/io/IOException", "ioctl(TUNSETIFF) failed (%s)",
+ strerror(errno));
+ return -1;
+ }
+
+ return fd;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* env, jobject clazz,
+ jstring platSubnet,
+ jint plat_suffix, jint mark) {
+ ScopedUtfChars platSubnetStr(env, platSubnet);
+
+ in6_addr plat_subnet;
+ if (inet_pton(AF_INET6, platSubnetStr.c_str(), &plat_subnet) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid plat prefix address %s",
+ platSubnetStr.c_str());
+ return -1;
+ }
+
+ int ret = net::clat::detect_mtu(&plat_subnet, plat_suffix, mark);
+ if (ret < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "detect mtu failed: %s", strerror(-ret));
+ return -1;
+ }
+
+ return ret;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_openPacketSocket(JNIEnv* env,
+ jobject clazz) {
+ // Will eventually be bound to htons(ETH_P_IPV6) protocol,
+ // but only after appropriate bpf filter is attached.
+ int sock = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (sock < 0) {
+ throwIOException(env, "packet socket failed", errno);
+ return -1;
+ }
+ return sock;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_openRawSocket6(JNIEnv* env,
+ jobject clazz,
+ jint mark) {
+ int sock = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_RAW);
+ if (sock < 0) {
+ throwIOException(env, "raw socket failed", errno);
+ return -1;
+ }
+
+ // TODO: check the mark validation
+ if (mark != MARK_UNSET && setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
+ throwIOException(env, "could not set mark on raw socket", errno);
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+static void com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt(
+ JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+ int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
+ if (sock < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
+ return;
+ }
+
+ ScopedUtfChars addrStr(env, addr6);
+
+ in6_addr addr;
+ if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s",
+ addrStr.c_str());
+ return;
+ }
+
+ struct ipv6_mreq mreq = {addr, ifindex};
+ int ret = setsockopt(sock, SOL_IPV6, IPV6_JOIN_ANYCAST, &mreq, sizeof(mreq));
+ if (ret) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "setsockopt IPV6_JOIN_ANYCAST failed: %s",
+ strerror(errno));
+ return;
+ }
+}
+
+static void com_android_server_connectivity_ClatCoordinator_configurePacketSocket(
+ JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+ ScopedUtfChars addrStr(env, addr6);
+
+ int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
+ if (sock < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
+ return;
+ }
+
+ in6_addr addr;
+ if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s",
+ addrStr.c_str());
+ return;
+ }
+
+ int ret = net::clat::configure_packet_socket(sock, &addr, ifindex);
+ if (ret < 0) {
+ throwIOException(env, "configure packet socket failed", -ret);
+ return;
+ }
+}
+
+int initTracker(const std::string& iface, const std::string& pfx96, const std::string& v4,
+ const std::string& v6, net::clat::ClatdTracker* output) {
+ strlcpy(output->iface, iface.c_str(), sizeof(output->iface));
+ output->ifIndex = if_nametoindex(iface.c_str());
+ if (output->ifIndex == 0) {
+ ALOGE("interface %s not found", output->iface);
+ return -1;
+ }
+
+ unsigned len = snprintf(output->v4iface, sizeof(output->v4iface),
+ "%s%s", DEVICEPREFIX, iface.c_str());
+ if (len >= sizeof(output->v4iface)) {
+ ALOGE("interface name too long '%s'", output->v4iface);
+ return -1;
+ }
+
+ output->v4ifIndex = if_nametoindex(output->v4iface);
+ if (output->v4ifIndex == 0) {
+ ALOGE("v4-interface %s not found", output->v4iface);
+ return -1;
+ }
+
+ if (!inet_pton(AF_INET6, pfx96.c_str(), &output->pfx96)) {
+ ALOGE("invalid IPv6 address specified for plat prefix: %s", pfx96.c_str());
+ return -1;
+ }
+
+ if (!inet_pton(AF_INET, v4.c_str(), &output->v4)) {
+ ALOGE("Invalid IPv4 address %s", v4.c_str());
+ return -1;
+ }
+
+ if (!inet_pton(AF_INET6, v6.c_str(), &output->v6)) {
+ ALOGE("Invalid source address %s", v6.c_str());
+ return -1;
+ }
+
+ return 0;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_startClatd(
+ JNIEnv* env, jobject clazz, jobject tunJavaFd, jobject readSockJavaFd,
+ jobject writeSockJavaFd, jstring iface, jstring pfx96, jstring v4, jstring v6) {
+ ScopedUtfChars ifaceStr(env, iface);
+ ScopedUtfChars pfx96Str(env, pfx96);
+ ScopedUtfChars v4Str(env, v4);
+ ScopedUtfChars v6Str(env, v6);
+
+ int tunFd = netjniutils::GetNativeFileDescriptor(env, tunJavaFd);
+ if (tunFd < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid tun file descriptor");
+ return -1;
+ }
+
+ int readSock = netjniutils::GetNativeFileDescriptor(env, readSockJavaFd);
+ if (readSock < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid read socket");
+ return -1;
+ }
+
+ int writeSock = netjniutils::GetNativeFileDescriptor(env, writeSockJavaFd);
+ if (writeSock < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid write socket");
+ return -1;
+ }
+
+ // 1. create a throwaway socket to reserve a file descriptor number
+ int passedTunFd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (passedTunFd == -1) {
+ throwIOException(env, "socket(ipv6/udp) for tun fd failed", errno);
+ return -1;
+ }
+ int passedSockRead = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (passedSockRead == -1) {
+ throwIOException(env, "socket(ipv6/udp) for read socket failed", errno);
+ return -1;
+ }
+ int passedSockWrite = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (passedSockWrite == -1) {
+ throwIOException(env, "socket(ipv6/udp) for write socket failed", errno);
+ return -1;
+ }
+
+ // these are the FD we'll pass to clatd on the cli, so need it as a string
+ char passedTunFdStr[INT32_STRLEN];
+ char passedSockReadStr[INT32_STRLEN];
+ char passedSockWriteStr[INT32_STRLEN];
+ snprintf(passedTunFdStr, sizeof(passedTunFdStr), "%d", passedTunFd);
+ snprintf(passedSockReadStr, sizeof(passedSockReadStr), "%d", passedSockRead);
+ snprintf(passedSockWriteStr, sizeof(passedSockWriteStr), "%d", passedSockWrite);
+
+ // 2. we're going to use this as argv[0] to clatd to make ps output more useful
+ std::string progname("clatd-");
+ progname += ifaceStr.c_str();
+
+ // clang-format off
+ const char* args[] = {progname.c_str(),
+ "-i", ifaceStr.c_str(),
+ "-p", pfx96Str.c_str(),
+ "-4", v4Str.c_str(),
+ "-6", v6Str.c_str(),
+ "-t", passedTunFdStr,
+ "-r", passedSockReadStr,
+ "-w", passedSockWriteStr,
+ nullptr};
+ // clang-format on
+
+ // 3. register vfork requirement
+ posix_spawnattr_t attr;
+ if (int ret = posix_spawnattr_init(&attr)) {
+ throwIOException(env, "posix_spawnattr_init failed", ret);
+ return -1;
+ }
+
+ // TODO: use android::base::ScopeGuard.
+ if (int ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK)) {
+ posix_spawnattr_destroy(&attr);
+ throwIOException(env, "posix_spawnattr_setflags failed", ret);
+ return -1;
+ }
+
+ // 4. register dup2() action: this is what 'clears' the CLOEXEC flag
+ // on the tun fd that we want the child clatd process to inherit
+ // (this will happen after the vfork, and before the execve)
+ posix_spawn_file_actions_t fa;
+ if (int ret = posix_spawn_file_actions_init(&fa)) {
+ posix_spawnattr_destroy(&attr);
+ throwIOException(env, "posix_spawn_file_actions_init failed", ret);
+ return -1;
+ }
+
+ if (int ret = posix_spawn_file_actions_adddup2(&fa, tunFd, passedTunFd)) {
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+ throwIOException(env, "posix_spawn_file_actions_adddup2 for tun fd failed", ret);
+ return -1;
+ }
+ if (int ret = posix_spawn_file_actions_adddup2(&fa, readSock, passedSockRead)) {
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+ throwIOException(env, "posix_spawn_file_actions_adddup2 for read socket failed", ret);
+ return -1;
+ }
+ if (int ret = posix_spawn_file_actions_adddup2(&fa, writeSock, passedSockWrite)) {
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+ throwIOException(env, "posix_spawn_file_actions_adddup2 for write socket failed", ret);
+ return -1;
+ }
+
+ // 5. actually perform vfork/dup2/execve
+ pid_t pid;
+ if (int ret = posix_spawn(&pid, kClatdPath, &fa, &attr, (char* const*)args, nullptr)) {
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+ throwIOException(env, "posix_spawn failed", ret);
+ return -1;
+ }
+
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+
+ // 5. Start BPF if any
+ if (!net::clat::initMaps()) {
+ net::clat::ClatdTracker tracker = {};
+ if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
+ &tracker)) {
+ net::clat::maybeStartBpf(tracker);
+ }
+ }
+
+ return pid;
+}
+
+// Stop clatd process. SIGTERM with timeout first, if fail, SIGKILL.
+// See stopProcess() in system/netd/server/NetdConstants.cpp.
+// TODO: have a function stopProcess(int pid, const char *name) in common location and call it.
+static constexpr int WAITPID_ATTEMPTS = 50;
+static constexpr int WAITPID_RETRY_INTERVAL_US = 100000;
+
+static void stopClatdProcess(int pid) {
+ int err = kill(pid, SIGTERM);
+ if (err) {
+ err = errno;
+ }
+ if (err == ESRCH) {
+ ALOGE("clatd child process %d unexpectedly disappeared", pid);
+ return;
+ }
+ if (err) {
+ ALOGE("Error killing clatd child process %d: %s", pid, strerror(err));
+ }
+ int status = 0;
+ int ret = 0;
+ for (int count = 0; ret == 0 && count < WAITPID_ATTEMPTS; count++) {
+ usleep(WAITPID_RETRY_INTERVAL_US);
+ ret = waitpid(pid, &status, WNOHANG);
+ }
+ if (ret == 0) {
+ ALOGE("Failed to SIGTERM clatd pid=%d, try SIGKILL", pid);
+ // TODO: fix that kill failed or waitpid doesn't return.
+ kill(pid, SIGKILL);
+ ret = waitpid(pid, &status, 0);
+ }
+ if (ret == -1) {
+ ALOGE("Error waiting for clatd child process %d: %s", pid, strerror(errno));
+ } else {
+ ALOGD("clatd process %d terminated status=%d", pid, status);
+ }
+}
+
+static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jobject clazz,
+ jstring iface, jstring pfx96,
+ jstring v4, jstring v6,
+ jint pid) {
+ ScopedUtfChars ifaceStr(env, iface);
+ ScopedUtfChars pfx96Str(env, pfx96);
+ ScopedUtfChars v4Str(env, v4);
+ ScopedUtfChars v6Str(env, v6);
+
+ if (pid <= 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid pid");
+ return;
+ }
+
+ if (!net::clat::initMaps()) {
+ net::clat::ClatdTracker tracker = {};
+ if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
+ &tracker)) {
+ net::clat::maybeStopBpf(tracker);
+ }
+ }
+
+ stopClatdProcess(pid);
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"native_selectIpv4Address", "(Ljava/lang/String;I)Ljava/lang/String;",
+ (void*)com_android_server_connectivity_ClatCoordinator_selectIpv4Address},
+ {"native_generateIpv6Address",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+ (void*)com_android_server_connectivity_ClatCoordinator_generateIpv6Address},
+ {"native_createTunInterface", "(Ljava/lang/String;)I",
+ (void*)com_android_server_connectivity_ClatCoordinator_createTunInterface},
+ {"native_detectMtu", "(Ljava/lang/String;II)I",
+ (void*)com_android_server_connectivity_ClatCoordinator_detectMtu},
+ {"native_openPacketSocket", "()I",
+ (void*)com_android_server_connectivity_ClatCoordinator_openPacketSocket},
+ {"native_openRawSocket6", "(I)I",
+ (void*)com_android_server_connectivity_ClatCoordinator_openRawSocket6},
+ {"native_addAnycastSetsockopt", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
+ (void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt},
+ {"native_configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
+ (void*)com_android_server_connectivity_ClatCoordinator_configurePacketSocket},
+ {"native_startClatd",
+ "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/lang/"
+ "String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*)com_android_server_connectivity_ClatCoordinator_startClatd},
+ {"native_stopClatd",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V",
+ (void*)com_android_server_connectivity_ClatCoordinator_stopClatd},
+};
+
+int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/connectivity/ClatCoordinator",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 0012879..04d9671 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -20,6 +20,7 @@
namespace android {
int register_android_server_TestNetworkService(JNIEnv* env);
+int register_android_server_connectivity_ClatCoordinator(JNIEnv* env);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@@ -32,6 +33,10 @@
return JNI_ERR;
}
+ if (register_android_server_connectivity_ClatCoordinator(env) < 0) {
+ return JNI_ERR;
+ }
+
return JNI_VERSION_1_6;
}
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
deleted file mode 100644
index 119b64f..0000000
--- a/service/lint-baseline.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#isDataCapable`"
- errorLine1=" if (tm.isDataCapable()) {"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="787"
- column="20"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.content.Context#sendStickyBroadcast`"
- errorLine1=" mUserAllContext.sendStickyBroadcast(intent, options);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="2681"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.content.pm.PackageManager#getTargetSdkVersion`"
- errorLine1=" final int callingVersion = pm.getTargetSdkVersion(callingPackageName);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="5851"
- column="43"/>
- </issue>
-
-</issues>
diff --git a/service/native/Android.bp b/service/native/Android.bp
new file mode 100644
index 0000000..5816318
--- /dev/null
+++ b/service/native/Android.bp
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+cc_library {
+ name: "libtraffic_controller",
+ defaults: ["netd_defaults"],
+ srcs: [
+ "TrafficController.cpp",
+ ],
+ header_libs: [
+ "bpf_connectivity_headers",
+ "bpf_headers",
+ "bpf_syscall_wrappers",
+ ],
+ static_libs: [
+ "libnetdutils",
+ // TrafficController would use the constants of INetd so that add
+ // netd_aidl_interface-lateststable-ndk.
+ "netd_aidl_interface-lateststable-ndk",
+ ],
+ shared_libs: [
+ // TODO: Find a good way to remove libbase.
+ "libbase",
+ "libcutils",
+ "libutils",
+ "liblog",
+ ],
+ export_include_dirs: ["include"],
+ sanitize: {
+ cfi: true,
+ },
+ apex_available: [
+ "com.android.tethering",
+ ],
+ min_sdk_version: "30",
+}
+
+cc_library_shared {
+ name: "libtraffic_controller_jni",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "jni/*.cpp",
+ ],
+ header_libs: [
+ "bpf_connectivity_headers",
+ ],
+ static_libs: [
+ "libnetdutils",
+ "libtraffic_controller",
+ "netd_aidl_interface-lateststable-ndk",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libutils",
+ "libnativehelper",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+ min_sdk_version: "30",
+}
+
+cc_test {
+ name: "traffic_controller_unit_test",
+ test_suites: ["general-tests"],
+ require_root: true,
+ local_include_dirs: ["include"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ ],
+ srcs: [
+ "TrafficControllerTest.cpp",
+ ],
+ static_libs: [
+ "libbase",
+ "libgmock",
+ "liblog",
+ "libnetdutils",
+ "libtraffic_controller",
+ "libutils",
+ "netd_aidl_interface-lateststable-ndk",
+ ],
+}
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
new file mode 100644
index 0000000..97fcc43
--- /dev/null
+++ b/service/native/TrafficController.cpp
@@ -0,0 +1,904 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TrafficController"
+#include <inttypes.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/inet_diag.h>
+#include <linux/netlink.h>
+#include <linux/sock_diag.h>
+#include <linux/unistd.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <map>
+#include <mutex>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <netdutils/StatusOr.h>
+#include <netdutils/Syscalls.h>
+#include <netdutils/UidConstants.h>
+#include <netdutils/Utils.h>
+#include <private/android_filesystem_config.h>
+
+#include "TrafficController.h"
+#include "bpf/BpfMap.h"
+#include "netdutils/DumpWriter.h"
+
+namespace android {
+namespace net {
+
+using base::StringPrintf;
+using base::unique_fd;
+using bpf::BpfMap;
+using bpf::OVERFLOW_COUNTERSET;
+using bpf::synchronizeKernelRCU;
+using netdutils::DumpWriter;
+using netdutils::getIfaceList;
+using netdutils::NetlinkListener;
+using netdutils::NetlinkListenerInterface;
+using netdutils::ScopedIndent;
+using netdutils::Slice;
+using netdutils::sSyscalls;
+using netdutils::Status;
+using netdutils::statusFromErrno;
+using netdutils::StatusOr;
+using netdutils::status::ok;
+
+constexpr int kSockDiagMsgType = SOCK_DIAG_BY_FAMILY;
+constexpr int kSockDiagDoneMsgType = NLMSG_DONE;
+
+const char* TrafficController::LOCAL_DOZABLE = "fw_dozable";
+const char* TrafficController::LOCAL_STANDBY = "fw_standby";
+const char* TrafficController::LOCAL_POWERSAVE = "fw_powersave";
+const char* TrafficController::LOCAL_RESTRICTED = "fw_restricted";
+
+static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET,
+ "Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET");
+static_assert(BPF_PERMISSION_UPDATE_DEVICE_STATS == INetd::PERMISSION_UPDATE_DEVICE_STATS,
+ "Mismatch between BPF and AIDL permissions: PERMISSION_UPDATE_DEVICE_STATS");
+
+#define FLAG_MSG_TRANS(result, flag, value) \
+ do { \
+ if ((value) & (flag)) { \
+ (result).append(" " #flag); \
+ (value) &= ~(flag); \
+ } \
+ } while (0)
+
+const std::string uidMatchTypeToString(uint8_t match) {
+ std::string matchType;
+ FLAG_MSG_TRANS(matchType, HAPPY_BOX_MATCH, match);
+ FLAG_MSG_TRANS(matchType, PENALTY_BOX_MATCH, match);
+ FLAG_MSG_TRANS(matchType, DOZABLE_MATCH, match);
+ FLAG_MSG_TRANS(matchType, STANDBY_MATCH, match);
+ FLAG_MSG_TRANS(matchType, POWERSAVE_MATCH, match);
+ FLAG_MSG_TRANS(matchType, RESTRICTED_MATCH, match);
+ FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
+ if (match) {
+ return StringPrintf("Unknown match: %u", match);
+ }
+ return matchType;
+}
+
+bool TrafficController::hasUpdateDeviceStatsPermission(uid_t uid) {
+ // This implementation is the same logic as method ActivityManager#checkComponentPermission.
+ // It implies that the calling uid can never be the same as PER_USER_RANGE.
+ uint32_t appId = uid % PER_USER_RANGE;
+ return ((appId == AID_ROOT) || (appId == AID_SYSTEM) ||
+ mPrivilegedUser.find(appId) != mPrivilegedUser.end());
+}
+
+const std::string UidPermissionTypeToString(int permission) {
+ if (permission == INetd::PERMISSION_NONE) {
+ return "PERMISSION_NONE";
+ }
+ if (permission == INetd::PERMISSION_UNINSTALLED) {
+ // This should never appear in the map, complain loudly if it does.
+ return "PERMISSION_UNINSTALLED error!";
+ }
+ std::string permissionType;
+ FLAG_MSG_TRANS(permissionType, BPF_PERMISSION_INTERNET, permission);
+ FLAG_MSG_TRANS(permissionType, BPF_PERMISSION_UPDATE_DEVICE_STATS, permission);
+ if (permission) {
+ return StringPrintf("Unknown permission: %u", permission);
+ }
+ return permissionType;
+}
+
+StatusOr<std::unique_ptr<NetlinkListenerInterface>> TrafficController::makeSkDestroyListener() {
+ const auto& sys = sSyscalls.get();
+ ASSIGN_OR_RETURN(auto event, sys.eventfd(0, EFD_CLOEXEC));
+ const int domain = AF_NETLINK;
+ const int type = SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK;
+ const int protocol = NETLINK_INET_DIAG;
+ ASSIGN_OR_RETURN(auto sock, sys.socket(domain, type, protocol));
+
+ // TODO: if too many sockets are closed too quickly, we can overflow the socket buffer, and
+ // some entries in mCookieTagMap will not be freed. In order to fix this we would need to
+ // periodically dump all sockets and remove the tag entries for sockets that have been closed.
+ // For now, set a large-enough buffer that we can close hundreds of sockets without getting
+ // ENOBUFS and leaking mCookieTagMap entries.
+ int rcvbuf = 512 * 1024;
+ auto ret = sys.setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
+ if (!ret.ok()) {
+ ALOGW("Failed to set SkDestroyListener buffer size to %d: %s", rcvbuf, ret.msg().c_str());
+ }
+
+ sockaddr_nl addr = {
+ .nl_family = AF_NETLINK,
+ .nl_groups = 1 << (SKNLGRP_INET_TCP_DESTROY - 1) | 1 << (SKNLGRP_INET_UDP_DESTROY - 1) |
+ 1 << (SKNLGRP_INET6_TCP_DESTROY - 1) | 1 << (SKNLGRP_INET6_UDP_DESTROY - 1)};
+ RETURN_IF_NOT_OK(sys.bind(sock, addr));
+
+ const sockaddr_nl kernel = {.nl_family = AF_NETLINK};
+ RETURN_IF_NOT_OK(sys.connect(sock, kernel));
+
+ std::unique_ptr<NetlinkListenerInterface> listener =
+ std::make_unique<NetlinkListener>(std::move(event), std::move(sock), "SkDestroyListen");
+
+ return listener;
+}
+
+Status TrafficController::initMaps() {
+ std::lock_guard guard(mMutex);
+
+ RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH));
+ RETURN_IF_NOT_OK(mUidCounterSetMap.init(UID_COUNTERSET_MAP_PATH));
+ RETURN_IF_NOT_OK(mAppUidStatsMap.init(APP_UID_STATS_MAP_PATH));
+ RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
+ RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
+ RETURN_IF_NOT_OK(mIfaceIndexNameMap.init(IFACE_INDEX_NAME_MAP_PATH));
+ RETURN_IF_NOT_OK(mIfaceStatsMap.init(IFACE_STATS_MAP_PATH));
+
+ RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
+ RETURN_IF_NOT_OK(
+ mConfigurationMap.writeValue(UID_RULES_CONFIGURATION_KEY, DEFAULT_CONFIG, BPF_ANY));
+ RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
+ BPF_ANY));
+
+ RETURN_IF_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH));
+ RETURN_IF_NOT_OK(mUidOwnerMap.clear());
+ RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+
+ return netdutils::status::ok;
+}
+
+Status TrafficController::start() {
+ RETURN_IF_NOT_OK(initMaps());
+
+ // Fetch the list of currently-existing interfaces. At this point NetlinkHandler is
+ // already running, so it will call addInterface() when any new interface appears.
+ // TODO: Clean-up addInterface() after interface monitoring is in
+ // NetworkStatsService.
+ std::map<std::string, uint32_t> ifacePairs;
+ ASSIGN_OR_RETURN(ifacePairs, getIfaceList());
+ for (const auto& ifacePair:ifacePairs) {
+ addInterface(ifacePair.first.c_str(), ifacePair.second);
+ }
+
+ auto result = makeSkDestroyListener();
+ if (!isOk(result)) {
+ ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
+ } else {
+ mSkDestroyListener = std::move(result.value());
+ }
+ // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
+ const auto rxHandler = [this](const nlmsghdr&, const Slice msg) {
+ std::lock_guard guard(mMutex);
+ inet_diag_msg diagmsg = {};
+ if (extract(msg, diagmsg) < sizeof(inet_diag_msg)) {
+ ALOGE("Unrecognized netlink message: %s", toString(msg).c_str());
+ return;
+ }
+ uint64_t sock_cookie = static_cast<uint64_t>(diagmsg.id.idiag_cookie[0]) |
+ (static_cast<uint64_t>(diagmsg.id.idiag_cookie[1]) << 32);
+
+ Status s = mCookieTagMap.deleteValue(sock_cookie);
+ if (!isOk(s) && s.code() != ENOENT) {
+ ALOGE("Failed to delete cookie %" PRIx64 ": %s", sock_cookie, toString(s).c_str());
+ return;
+ }
+ };
+ expectOk(mSkDestroyListener->subscribe(kSockDiagMsgType, rxHandler));
+
+ // In case multiple netlink message comes in as a stream, we need to handle the rxDone message
+ // properly.
+ const auto rxDoneHandler = [](const nlmsghdr&, const Slice msg) {
+ // Ignore NLMSG_DONE messages
+ inet_diag_msg diagmsg = {};
+ extract(msg, diagmsg);
+ };
+ expectOk(mSkDestroyListener->subscribe(kSockDiagDoneMsgType, rxDoneHandler));
+
+ return netdutils::status::ok;
+}
+
+int TrafficController::setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) {
+ if (counterSetNum < 0 || counterSetNum >= OVERFLOW_COUNTERSET) return -EINVAL;
+
+ std::lock_guard guard(mMutex);
+ if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
+
+ // The default counter set for all uid is 0, so deleting the current counterset for that uid
+ // will automatically set it to 0.
+ if (counterSetNum == 0) {
+ Status res = mUidCounterSetMap.deleteValue(uid);
+ if (isOk(res) || (!isOk(res) && res.code() == ENOENT)) {
+ return 0;
+ } else {
+ ALOGE("Failed to delete the counterSet: %s\n", strerror(res.code()));
+ return -res.code();
+ }
+ }
+ uint8_t tmpCounterSetNum = (uint8_t)counterSetNum;
+ Status res = mUidCounterSetMap.writeValue(uid, tmpCounterSetNum, BPF_ANY);
+ if (!isOk(res)) {
+ ALOGE("Failed to set the counterSet: %s, fd: %d", strerror(res.code()),
+ mUidCounterSetMap.getMap().get());
+ return -res.code();
+ }
+ return 0;
+}
+
+// This method only get called by system_server when an app get uinstalled, it
+// is called inside removeUidsLocked() while holding mStatsLock. So it is safe
+// to iterate and modify the stats maps.
+int TrafficController::deleteTagData(uint32_t tag, uid_t uid, uid_t callingUid) {
+ std::lock_guard guard(mMutex);
+ if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
+
+ // First we go through the cookieTagMap to delete the target uid tag combination. Or delete all
+ // the tags related to the uid if the tag is 0.
+ const auto deleteMatchedCookieEntries = [uid, tag](const uint64_t& key,
+ const UidTagValue& value,
+ BpfMap<uint64_t, UidTagValue>& map) {
+ if (value.uid == uid && (value.tag == tag || tag == 0)) {
+ auto res = map.deleteValue(key);
+ if (res.ok() || (res.error().code() == ENOENT)) {
+ return base::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 base::Result<void>();
+ };
+ mCookieTagMap.iterateWithValue(deleteMatchedCookieEntries);
+ // Now we go through the Tag stats map and delete the data entry with correct uid and tag
+ // combination. Or all tag stats under that uid if the target tag is 0.
+ const auto deleteMatchedUidTagEntries = [uid, tag](const StatsKey& key,
+ BpfMap<StatsKey, StatsValue>& map) {
+ if (key.uid == uid && (key.tag == tag || tag == 0)) {
+ auto res = map.deleteValue(key);
+ if (res.ok() || (res.error().code() == ENOENT)) {
+ //Entry is deleted, use the current key to get a new nextKey;
+ return base::Result<void>();
+ }
+ ALOGE("Failed to delete data(uid=%u, tag=%u): %s\n", key.uid, key.tag,
+ strerror(res.error().code()));
+ }
+ return base::Result<void>();
+ };
+ mStatsMapB.iterate(deleteMatchedUidTagEntries);
+ mStatsMapA.iterate(deleteMatchedUidTagEntries);
+ // If the tag is not zero, we already deleted all the data entry required. If tag is 0, we also
+ // need to delete the stats stored in uidStatsMap and counterSet map.
+ if (tag != 0) return 0;
+
+ auto res = mUidCounterSetMap.deleteValue(uid);
+ if (!res.ok() && res.error().code() != ENOENT) {
+ ALOGE("Failed to delete counterSet data(uid=%u, tag=%u): %s\n", uid, tag,
+ strerror(res.error().code()));
+ }
+
+ auto deleteAppUidStatsEntry = [uid](const uint32_t& key,
+ BpfMap<uint32_t, StatsValue>& map) -> base::Result<void> {
+ if (key == uid) {
+ auto res = map.deleteValue(key);
+ if (res.ok() || (res.error().code() == ENOENT)) {
+ return {};
+ }
+ ALOGE("Failed to delete data(uid=%u): %s", key, strerror(res.error().code()));
+ }
+ return {};
+ };
+ mAppUidStatsMap.iterate(deleteAppUidStatsEntry);
+ return 0;
+}
+
+int TrafficController::addInterface(const char* name, uint32_t ifaceIndex) {
+ IfaceValue iface;
+ if (ifaceIndex == 0) {
+ ALOGE("Unknown interface %s(%d)", name, ifaceIndex);
+ return -1;
+ }
+
+ strlcpy(iface.name, name, sizeof(IfaceValue));
+ Status res = mIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY);
+ if (!isOk(res)) {
+ ALOGE("Failed to add iface %s(%d): %s", name, ifaceIndex, strerror(res.code()));
+ return -res.code();
+ }
+ return 0;
+}
+
+Status TrafficController::updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
+ FirewallType type) {
+ std::lock_guard guard(mMutex);
+ if ((rule == ALLOW && type == ALLOWLIST) || (rule == DENY && type == DENYLIST)) {
+ RETURN_IF_NOT_OK(addRule(uid, match));
+ } else if ((rule == ALLOW && type == DENYLIST) || (rule == DENY && type == ALLOWLIST)) {
+ RETURN_IF_NOT_OK(removeRule(uid, match));
+ } else {
+ //Cannot happen.
+ return statusFromErrno(EINVAL, "");
+ }
+ return netdutils::status::ok;
+}
+
+Status TrafficController::removeRule(uint32_t uid, UidOwnerMatchType match) {
+ auto oldMatch = mUidOwnerMap.readValue(uid);
+ if (oldMatch.ok()) {
+ UidOwnerValue newMatch = {
+ .iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif,
+ .rule = static_cast<uint8_t>(oldMatch.value().rule & ~match),
+ };
+ if (newMatch.rule == 0) {
+ RETURN_IF_NOT_OK(mUidOwnerMap.deleteValue(uid));
+ } else {
+ RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
+ }
+ } else {
+ return statusFromErrno(ENOENT, StringPrintf("uid: %u does not exist in map", uid));
+ }
+ return netdutils::status::ok;
+}
+
+Status TrafficController::addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif) {
+ // iif should be non-zero if and only if match == MATCH_IIF
+ if (match == IIF_MATCH && iif == 0) {
+ return statusFromErrno(EINVAL, "Interface match must have nonzero interface index");
+ } else if (match != IIF_MATCH && iif != 0) {
+ return statusFromErrno(EINVAL, "Non-interface match must have zero interface index");
+ }
+ auto oldMatch = mUidOwnerMap.readValue(uid);
+ if (oldMatch.ok()) {
+ UidOwnerValue newMatch = {
+ .iif = iif ? iif : oldMatch.value().iif,
+ .rule = static_cast<uint8_t>(oldMatch.value().rule | match),
+ };
+ RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
+ } else {
+ UidOwnerValue newMatch = {
+ .iif = iif,
+ .rule = static_cast<uint8_t>(match),
+ };
+ RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
+ }
+ return netdutils::status::ok;
+}
+
+Status TrafficController::updateUidOwnerMap(const uint32_t uid,
+ UidOwnerMatchType matchType, IptOp op) {
+ std::lock_guard guard(mMutex);
+ if (op == IptOpDelete) {
+ RETURN_IF_NOT_OK(removeRule(uid, matchType));
+ } else if (op == IptOpInsert) {
+ RETURN_IF_NOT_OK(addRule(uid, matchType));
+ } else {
+ // Cannot happen.
+ return statusFromErrno(EINVAL, StringPrintf("invalid IptOp: %d, %d", op, matchType));
+ }
+ return netdutils::status::ok;
+}
+
+FirewallType TrafficController::getFirewallType(ChildChain chain) {
+ switch (chain) {
+ case DOZABLE:
+ return ALLOWLIST;
+ case STANDBY:
+ return DENYLIST;
+ case POWERSAVE:
+ return ALLOWLIST;
+ case RESTRICTED:
+ return ALLOWLIST;
+ case NONE:
+ default:
+ return DENYLIST;
+ }
+}
+
+int TrafficController::changeUidOwnerRule(ChildChain chain, uid_t uid, FirewallRule rule,
+ FirewallType type) {
+ Status res;
+ switch (chain) {
+ case DOZABLE:
+ res = updateOwnerMapEntry(DOZABLE_MATCH, uid, rule, type);
+ break;
+ case STANDBY:
+ res = updateOwnerMapEntry(STANDBY_MATCH, uid, rule, type);
+ break;
+ case POWERSAVE:
+ res = updateOwnerMapEntry(POWERSAVE_MATCH, uid, rule, type);
+ break;
+ case RESTRICTED:
+ res = updateOwnerMapEntry(RESTRICTED_MATCH, uid, rule, type);
+ break;
+ case NONE:
+ default:
+ ALOGW("Unknown child chain: %d", chain);
+ return -EINVAL;
+ }
+ if (!isOk(res)) {
+ ALOGE("change uid(%u) rule of %d failed: %s, rule: %d, type: %d", uid, chain,
+ res.msg().c_str(), rule, type);
+ return -res.code();
+ }
+ return 0;
+}
+
+Status TrafficController::replaceRulesInMap(const UidOwnerMatchType match,
+ const std::vector<int32_t>& uids) {
+ std::lock_guard guard(mMutex);
+ std::set<int32_t> uidSet(uids.begin(), uids.end());
+ std::vector<uint32_t> uidsToDelete;
+ auto getUidsToDelete = [&uidsToDelete, &uidSet](const uint32_t& key,
+ const BpfMap<uint32_t, UidOwnerValue>&) {
+ if (uidSet.find((int32_t) key) == uidSet.end()) {
+ uidsToDelete.push_back(key);
+ }
+ return base::Result<void>();
+ };
+ RETURN_IF_NOT_OK(mUidOwnerMap.iterate(getUidsToDelete));
+
+ for(auto uid : uidsToDelete) {
+ RETURN_IF_NOT_OK(removeRule(uid, match));
+ }
+
+ for (auto uid : uids) {
+ RETURN_IF_NOT_OK(addRule(uid, match));
+ }
+ return netdutils::status::ok;
+}
+
+Status TrafficController::addUidInterfaceRules(const int iif,
+ const std::vector<int32_t>& uidsToAdd) {
+ if (!iif) {
+ return statusFromErrno(EINVAL, "Interface rule must specify interface");
+ }
+ std::lock_guard guard(mMutex);
+
+ for (auto uid : uidsToAdd) {
+ netdutils::Status result = addRule(uid, IIF_MATCH, iif);
+ if (!isOk(result)) {
+ ALOGW("addRule failed(%d): uid=%d iif=%d", result.code(), uid, iif);
+ }
+ }
+ return netdutils::status::ok;
+}
+
+Status TrafficController::removeUidInterfaceRules(const std::vector<int32_t>& uidsToDelete) {
+ std::lock_guard guard(mMutex);
+
+ for (auto uid : uidsToDelete) {
+ netdutils::Status result = removeRule(uid, IIF_MATCH);
+ if (!isOk(result)) {
+ ALOGW("removeRule failed(%d): uid=%d", result.code(), uid);
+ }
+ }
+ return netdutils::status::ok;
+}
+
+int TrafficController::replaceUidOwnerMap(const std::string& name, bool isAllowlist __unused,
+ const std::vector<int32_t>& uids) {
+ // FirewallRule rule = isAllowlist ? ALLOW : DENY;
+ // FirewallType type = isAllowlist ? ALLOWLIST : DENYLIST;
+ Status res;
+ if (!name.compare(LOCAL_DOZABLE)) {
+ res = replaceRulesInMap(DOZABLE_MATCH, uids);
+ } else if (!name.compare(LOCAL_STANDBY)) {
+ res = replaceRulesInMap(STANDBY_MATCH, uids);
+ } else if (!name.compare(LOCAL_POWERSAVE)) {
+ res = replaceRulesInMap(POWERSAVE_MATCH, uids);
+ } else if (!name.compare(LOCAL_RESTRICTED)) {
+ res = replaceRulesInMap(RESTRICTED_MATCH, uids);
+ } else {
+ ALOGE("unknown chain name: %s", name.c_str());
+ return -EINVAL;
+ }
+ if (!isOk(res)) {
+ ALOGE("Failed to clean up chain: %s: %s", name.c_str(), res.msg().c_str());
+ return -res.code();
+ }
+ return 0;
+}
+
+int TrafficController::toggleUidOwnerMap(ChildChain chain, bool enable) {
+ std::lock_guard guard(mMutex);
+ uint32_t key = UID_RULES_CONFIGURATION_KEY;
+ auto oldConfiguration = mConfigurationMap.readValue(key);
+ if (!oldConfiguration.ok()) {
+ ALOGE("Cannot read the old configuration from map: %s",
+ oldConfiguration.error().message().c_str());
+ return -oldConfiguration.error().code();
+ }
+ Status res;
+ BpfConfig newConfiguration;
+ uint8_t match;
+ switch (chain) {
+ case DOZABLE:
+ match = DOZABLE_MATCH;
+ break;
+ case STANDBY:
+ match = STANDBY_MATCH;
+ break;
+ case POWERSAVE:
+ match = POWERSAVE_MATCH;
+ break;
+ case RESTRICTED:
+ match = RESTRICTED_MATCH;
+ break;
+ default:
+ return -EINVAL;
+ }
+ newConfiguration =
+ enable ? (oldConfiguration.value() | match) : (oldConfiguration.value() & (~match));
+ res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
+ if (!isOk(res)) {
+ ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str());
+ }
+ return -res.code();
+}
+
+Status TrafficController::swapActiveStatsMap() {
+ std::lock_guard guard(mMutex);
+
+ uint32_t key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+ auto oldConfiguration = mConfigurationMap.readValue(key);
+ if (!oldConfiguration.ok()) {
+ ALOGE("Cannot read the old configuration from map: %s",
+ oldConfiguration.error().message().c_str());
+ return Status(oldConfiguration.error().code(), oldConfiguration.error().message());
+ }
+
+ // Write to the configuration map to inform the kernel eBPF program to switch
+ // from using one map to the other. Use flag BPF_EXIST here since the map should
+ // be already populated in initMaps.
+ uint8_t newConfigure = (oldConfiguration.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A;
+ auto res = mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, newConfigure,
+ BPF_EXIST);
+ if (!res.ok()) {
+ ALOGE("Failed to toggle the stats map: %s", strerror(res.error().code()));
+ return res;
+ }
+ // After changing the config, we need to make sure all the current running
+ // eBPF programs are finished and all the CPUs are aware of this config change
+ // before we modify the old map. So we do a special hack here to wait for
+ // the kernel to do a synchronize_rcu(). Once the kernel called
+ // synchronize_rcu(), the config we just updated will be available to all cores
+ // and the next eBPF programs triggered inside the kernel will use the new
+ // map configuration. So once this function returns we can safely modify the
+ // old stats map without concerning about race between the kernel and
+ // userspace.
+ int ret = synchronizeKernelRCU();
+ if (ret) {
+ ALOGE("map swap synchronize_rcu() ended with failure: %s", strerror(-ret));
+ return statusFromErrno(-ret, "map swap synchronize_rcu() failed");
+ }
+ return netdutils::status::ok;
+}
+
+void TrafficController::setPermissionForUids(int permission, const std::vector<uid_t>& uids) {
+ std::lock_guard guard(mMutex);
+ if (permission == INetd::PERMISSION_UNINSTALLED) {
+ for (uid_t uid : uids) {
+ // Clean up all permission information for the related uid if all the
+ // packages related to it are uninstalled.
+ mPrivilegedUser.erase(uid);
+ Status ret = mUidPermissionMap.deleteValue(uid);
+ if (!isOk(ret) && ret.code() != ENOENT) {
+ ALOGE("Failed to clean up the permission for %u: %s", uid, strerror(ret.code()));
+ }
+ }
+ return;
+ }
+
+ bool privileged = (permission & INetd::PERMISSION_UPDATE_DEVICE_STATS);
+
+ for (uid_t uid : uids) {
+ if (privileged) {
+ mPrivilegedUser.insert(uid);
+ } else {
+ mPrivilegedUser.erase(uid);
+ }
+
+ // The map stores all the permissions that the UID has, except if the only permission
+ // the UID has is the INTERNET permission, then the UID should not appear in the map.
+ if (permission != INetd::PERMISSION_INTERNET) {
+ Status ret = mUidPermissionMap.writeValue(uid, permission, BPF_ANY);
+ if (!isOk(ret)) {
+ ALOGE("Failed to set permission: %s of uid(%u) to permission map: %s",
+ UidPermissionTypeToString(permission).c_str(), uid, strerror(ret.code()));
+ }
+ } else {
+ Status ret = mUidPermissionMap.deleteValue(uid);
+ if (!isOk(ret) && ret.code() != ENOENT) {
+ ALOGE("Failed to remove uid %u from permission map: %s", uid, strerror(ret.code()));
+ }
+ }
+ }
+}
+
+std::string getProgramStatus(const char *path) {
+ int ret = access(path, R_OK);
+ if (ret == 0) {
+ return StringPrintf("OK");
+ }
+ if (ret != 0 && errno == ENOENT) {
+ return StringPrintf("program is missing at: %s", path);
+ }
+ return StringPrintf("check Program %s error: %s", path, strerror(errno));
+}
+
+std::string getMapStatus(const base::unique_fd& map_fd, const char* path) {
+ if (map_fd.get() < 0) {
+ return StringPrintf("map fd lost");
+ }
+ if (access(path, F_OK) != 0) {
+ return StringPrintf("map not pinned to location: %s", path);
+ }
+ return StringPrintf("OK");
+}
+
+// NOLINTNEXTLINE(google-runtime-references): grandfathered pass by non-const reference
+void dumpBpfMap(const std::string& mapName, DumpWriter& dw, const std::string& header) {
+ dw.blankline();
+ dw.println("%s:", mapName.c_str());
+ if (!header.empty()) {
+ dw.println(header);
+ }
+}
+
+const String16 TrafficController::DUMP_KEYWORD = String16("trafficcontroller");
+
+void TrafficController::dump(DumpWriter& dw, bool verbose) {
+ std::lock_guard guard(mMutex);
+ ScopedIndent indentTop(dw);
+ dw.println("TrafficController");
+
+ ScopedIndent indentPreBpfModule(dw);
+
+ dw.blankline();
+ dw.println("mCookieTagMap status: %s",
+ getMapStatus(mCookieTagMap.getMap(), COOKIE_TAG_MAP_PATH).c_str());
+ dw.println("mUidCounterSetMap status: %s",
+ getMapStatus(mUidCounterSetMap.getMap(), UID_COUNTERSET_MAP_PATH).c_str());
+ dw.println("mAppUidStatsMap status: %s",
+ getMapStatus(mAppUidStatsMap.getMap(), APP_UID_STATS_MAP_PATH).c_str());
+ dw.println("mStatsMapA status: %s",
+ getMapStatus(mStatsMapA.getMap(), STATS_MAP_A_PATH).c_str());
+ dw.println("mStatsMapB status: %s",
+ getMapStatus(mStatsMapB.getMap(), STATS_MAP_B_PATH).c_str());
+ dw.println("mIfaceIndexNameMap status: %s",
+ getMapStatus(mIfaceIndexNameMap.getMap(), IFACE_INDEX_NAME_MAP_PATH).c_str());
+ dw.println("mIfaceStatsMap status: %s",
+ getMapStatus(mIfaceStatsMap.getMap(), IFACE_STATS_MAP_PATH).c_str());
+ dw.println("mConfigurationMap status: %s",
+ getMapStatus(mConfigurationMap.getMap(), CONFIGURATION_MAP_PATH).c_str());
+ dw.println("mUidOwnerMap status: %s",
+ getMapStatus(mUidOwnerMap.getMap(), UID_OWNER_MAP_PATH).c_str());
+
+ dw.blankline();
+ dw.println("Cgroup ingress program status: %s",
+ getProgramStatus(BPF_INGRESS_PROG_PATH).c_str());
+ dw.println("Cgroup egress program status: %s", getProgramStatus(BPF_EGRESS_PROG_PATH).c_str());
+ dw.println("xt_bpf ingress program status: %s",
+ getProgramStatus(XT_BPF_INGRESS_PROG_PATH).c_str());
+ dw.println("xt_bpf egress program status: %s",
+ getProgramStatus(XT_BPF_EGRESS_PROG_PATH).c_str());
+ dw.println("xt_bpf bandwidth allowlist program status: %s",
+ getProgramStatus(XT_BPF_ALLOWLIST_PROG_PATH).c_str());
+ dw.println("xt_bpf bandwidth denylist program status: %s",
+ getProgramStatus(XT_BPF_DENYLIST_PROG_PATH).c_str());
+
+ if (!verbose) {
+ return;
+ }
+
+ dw.blankline();
+ dw.println("BPF map content:");
+
+ ScopedIndent indentForMapContent(dw);
+
+ // Print CookieTagMap content.
+ dumpBpfMap("mCookieTagMap", dw, "");
+ const auto printCookieTagInfo = [&dw](const uint64_t& key, const UidTagValue& value,
+ const BpfMap<uint64_t, UidTagValue>&) {
+ dw.println("cookie=%" PRIu64 " tag=0x%x uid=%u", key, value.tag, value.uid);
+ return base::Result<void>();
+ };
+ base::Result<void> res = mCookieTagMap.iterateWithValue(printCookieTagInfo);
+ if (!res.ok()) {
+ dw.println("mCookieTagMap print end with error: %s", res.error().message().c_str());
+ }
+
+ // Print UidCounterSetMap content.
+ dumpBpfMap("mUidCounterSetMap", dw, "");
+ const auto printUidInfo = [&dw](const uint32_t& key, const uint8_t& value,
+ const BpfMap<uint32_t, uint8_t>&) {
+ dw.println("%u %u", key, value);
+ return base::Result<void>();
+ };
+ res = mUidCounterSetMap.iterateWithValue(printUidInfo);
+ if (!res.ok()) {
+ dw.println("mUidCounterSetMap print end with error: %s", res.error().message().c_str());
+ }
+
+ // Print AppUidStatsMap content.
+ std::string appUidStatsHeader = StringPrintf("uid rxBytes rxPackets txBytes txPackets");
+ dumpBpfMap("mAppUidStatsMap:", dw, appUidStatsHeader);
+ auto printAppUidStatsInfo = [&dw](const uint32_t& key, const StatsValue& value,
+ const BpfMap<uint32_t, StatsValue>&) {
+ dw.println("%u %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, key, value.rxBytes,
+ value.rxPackets, value.txBytes, value.txPackets);
+ return base::Result<void>();
+ };
+ res = mAppUidStatsMap.iterateWithValue(printAppUidStatsInfo);
+ if (!res.ok()) {
+ dw.println("mAppUidStatsMap print end with error: %s", res.error().message().c_str());
+ }
+
+ // Print uidStatsMap content.
+ std::string statsHeader = StringPrintf("ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes"
+ " rxPackets txBytes txPackets");
+ dumpBpfMap("mStatsMapA", dw, statsHeader);
+ const auto printStatsInfo = [&dw, this](const StatsKey& key, const StatsValue& value,
+ const BpfMap<StatsKey, StatsValue>&) {
+ uint32_t ifIndex = key.ifaceIndex;
+ auto ifname = mIfaceIndexNameMap.readValue(ifIndex);
+ if (!ifname.ok()) {
+ ifname = IfaceValue{"unknown"};
+ }
+ dw.println("%u %s 0x%x %u %u %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, ifIndex,
+ ifname.value().name, key.tag, key.uid, key.counterSet, value.rxBytes,
+ value.rxPackets, value.txBytes, value.txPackets);
+ return base::Result<void>();
+ };
+ res = mStatsMapA.iterateWithValue(printStatsInfo);
+ if (!res.ok()) {
+ dw.println("mStatsMapA print end with error: %s", res.error().message().c_str());
+ }
+
+ // Print TagStatsMap content.
+ dumpBpfMap("mStatsMapB", dw, statsHeader);
+ res = mStatsMapB.iterateWithValue(printStatsInfo);
+ if (!res.ok()) {
+ dw.println("mStatsMapB print end with error: %s", res.error().message().c_str());
+ }
+
+ // Print ifaceIndexToNameMap content.
+ dumpBpfMap("mIfaceIndexNameMap", dw, "");
+ const auto printIfaceNameInfo = [&dw](const uint32_t& key, const IfaceValue& value,
+ const BpfMap<uint32_t, IfaceValue>&) {
+ const char* ifname = value.name;
+ dw.println("ifaceIndex=%u ifaceName=%s", key, ifname);
+ return base::Result<void>();
+ };
+ res = mIfaceIndexNameMap.iterateWithValue(printIfaceNameInfo);
+ if (!res.ok()) {
+ dw.println("mIfaceIndexNameMap print end with error: %s", res.error().message().c_str());
+ }
+
+ // Print ifaceStatsMap content
+ std::string ifaceStatsHeader = StringPrintf("ifaceIndex ifaceName rxBytes rxPackets txBytes"
+ " txPackets");
+ dumpBpfMap("mIfaceStatsMap:", dw, ifaceStatsHeader);
+ const auto printIfaceStatsInfo = [&dw, this](const uint32_t& key, const StatsValue& value,
+ const BpfMap<uint32_t, StatsValue>&) {
+ auto ifname = mIfaceIndexNameMap.readValue(key);
+ if (!ifname.ok()) {
+ ifname = IfaceValue{"unknown"};
+ }
+ dw.println("%u %s %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, key, ifname.value().name,
+ value.rxBytes, value.rxPackets, value.txBytes, value.txPackets);
+ return base::Result<void>();
+ };
+ res = mIfaceStatsMap.iterateWithValue(printIfaceStatsInfo);
+ if (!res.ok()) {
+ dw.println("mIfaceStatsMap print end with error: %s", res.error().message().c_str());
+ }
+
+ dw.blankline();
+
+ uint32_t key = UID_RULES_CONFIGURATION_KEY;
+ auto configuration = mConfigurationMap.readValue(key);
+ if (configuration.ok()) {
+ dw.println("current ownerMatch configuration: %d%s", configuration.value(),
+ uidMatchTypeToString(configuration.value()).c_str());
+ } else {
+ dw.println("mConfigurationMap read ownerMatch configure failed with error: %s",
+ configuration.error().message().c_str());
+ }
+
+ key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+ configuration = mConfigurationMap.readValue(key);
+ if (configuration.ok()) {
+ const char* statsMapDescription = "???";
+ switch (configuration.value()) {
+ case SELECT_MAP_A:
+ statsMapDescription = "SELECT_MAP_A";
+ break;
+ case SELECT_MAP_B:
+ statsMapDescription = "SELECT_MAP_B";
+ break;
+ // No default clause, so if we ever add a third map, this code will fail to build.
+ }
+ dw.println("current statsMap configuration: %d %s", configuration.value(),
+ statsMapDescription);
+ } else {
+ dw.println("mConfigurationMap read stats map configure failed with error: %s",
+ configuration.error().message().c_str());
+ }
+ dumpBpfMap("mUidOwnerMap", dw, "");
+ const auto printUidMatchInfo = [&dw, this](const uint32_t& key, const UidOwnerValue& value,
+ const BpfMap<uint32_t, UidOwnerValue>&) {
+ if (value.rule & IIF_MATCH) {
+ auto ifname = mIfaceIndexNameMap.readValue(value.iif);
+ if (ifname.ok()) {
+ dw.println("%u %s %s", key, uidMatchTypeToString(value.rule).c_str(),
+ ifname.value().name);
+ } else {
+ dw.println("%u %s %u", key, uidMatchTypeToString(value.rule).c_str(), value.iif);
+ }
+ } else {
+ dw.println("%u %s", key, uidMatchTypeToString(value.rule).c_str());
+ }
+ return base::Result<void>();
+ };
+ res = mUidOwnerMap.iterateWithValue(printUidMatchInfo);
+ if (!res.ok()) {
+ dw.println("mUidOwnerMap print end with error: %s", res.error().message().c_str());
+ }
+ dumpBpfMap("mUidPermissionMap", dw, "");
+ const auto printUidPermissionInfo = [&dw](const uint32_t& key, const int& value,
+ const BpfMap<uint32_t, uint8_t>&) {
+ dw.println("%u %s", key, UidPermissionTypeToString(value).c_str());
+ return base::Result<void>();
+ };
+ res = mUidPermissionMap.iterateWithValue(printUidPermissionInfo);
+ if (!res.ok()) {
+ dw.println("mUidPermissionMap print end with error: %s", res.error().message().c_str());
+ }
+
+ dumpBpfMap("mPrivilegedUser", dw, "");
+ for (uid_t uid : mPrivilegedUser) {
+ dw.println("%u ALLOW_UPDATE_DEVICE_STATS", (uint32_t)uid);
+ }
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
new file mode 100644
index 0000000..f401636
--- /dev/null
+++ b/service/native/TrafficControllerTest.cpp
@@ -0,0 +1,750 @@
+/*
+ * 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.
+ *
+ * TrafficControllerTest.cpp - unit tests for TrafficController.cpp
+ */
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <binder/Status.h>
+
+#include <netdutils/MockSyscalls.h>
+
+#include "TrafficController.h"
+#include "bpf/BpfUtils.h"
+
+using namespace android::bpf; // NOLINT(google-build-using-namespace): grandfathered
+
+namespace android {
+namespace net {
+
+using android::netdutils::Status;
+using base::Result;
+using netdutils::isOk;
+
+constexpr int TEST_MAP_SIZE = 10;
+constexpr uid_t TEST_UID = 10086;
+constexpr uid_t TEST_UID2 = 54321;
+constexpr uid_t TEST_UID3 = 98765;
+constexpr uint32_t TEST_TAG = 42;
+constexpr uint32_t TEST_COUNTERSET = 1;
+constexpr uint32_t DEFAULT_COUNTERSET = 0;
+
+#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
+
+class TrafficControllerTest : public ::testing::Test {
+ protected:
+ TrafficControllerTest() {}
+ TrafficController mTc;
+ BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
+ BpfMap<uint32_t, uint8_t> mFakeUidCounterSetMap;
+ BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
+ BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
+ BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+ BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
+ BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+
+ void SetUp() {
+ std::lock_guard guard(mTc.mMutex);
+ ASSERT_EQ(0, setrlimitForTest());
+
+ mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
+ TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeCookieTagMap);
+
+ mFakeUidCounterSetMap.reset(
+ createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeUidCounterSetMap);
+
+ mFakeAppUidStatsMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(StatsValue),
+ TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeAppUidStatsMap);
+
+ mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
+ TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeStatsMapA);
+
+ mFakeConfigurationMap.reset(
+ createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+ ASSERT_VALID(mFakeConfigurationMap);
+
+ mFakeUidOwnerMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(UidOwnerValue),
+ TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeUidOwnerMap);
+ mFakeUidPermissionMap.reset(
+ createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeUidPermissionMap);
+
+ mTc.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+ ASSERT_VALID(mTc.mCookieTagMap);
+ mTc.mUidCounterSetMap.reset(dupFd(mFakeUidCounterSetMap.getMap()));
+ ASSERT_VALID(mTc.mUidCounterSetMap);
+ mTc.mAppUidStatsMap.reset(dupFd(mFakeAppUidStatsMap.getMap()));
+ ASSERT_VALID(mTc.mAppUidStatsMap);
+ mTc.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+ ASSERT_VALID(mTc.mStatsMapA);
+ mTc.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+ ASSERT_VALID(mTc.mConfigurationMap);
+
+ // Always write to stats map A by default.
+ ASSERT_RESULT_OK(mTc.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
+ SELECT_MAP_A, BPF_ANY));
+ mTc.mUidOwnerMap.reset(dupFd(mFakeUidOwnerMap.getMap()));
+ ASSERT_VALID(mTc.mUidOwnerMap);
+ mTc.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+ ASSERT_VALID(mTc.mUidPermissionMap);
+ mTc.mPrivilegedUser.clear();
+ }
+
+ int dupFd(const android::base::unique_fd& mapFd) {
+ return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
+ }
+
+ void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
+ UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
+ EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
+ *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
+ StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
+ uint8_t counterSet = TEST_COUNTERSET;
+ EXPECT_RESULT_OK(mFakeUidCounterSetMap.writeValue(uid, counterSet, BPF_ANY));
+ EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+ key->tag = 0;
+ EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+ EXPECT_RESULT_OK(mFakeAppUidStatsMap.writeValue(uid, statsMapValue, BPF_ANY));
+ // put tag information back to statsKey
+ key->tag = tag;
+ }
+
+ void checkUidOwnerRuleForChain(ChildChain chain, UidOwnerMatchType match) {
+ uint32_t uid = TEST_UID;
+ EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, DENYLIST));
+ Result<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
+ EXPECT_RESULT_OK(value);
+ EXPECT_TRUE(value.value().rule & match);
+
+ uid = TEST_UID2;
+ EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, ALLOWLIST));
+ value = mFakeUidOwnerMap.readValue(uid);
+ EXPECT_RESULT_OK(value);
+ EXPECT_TRUE(value.value().rule & match);
+
+ EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, ALLOWLIST));
+ value = mFakeUidOwnerMap.readValue(uid);
+ EXPECT_FALSE(value.ok());
+ EXPECT_EQ(ENOENT, value.error().code());
+
+ uid = TEST_UID;
+ EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, DENYLIST));
+ value = mFakeUidOwnerMap.readValue(uid);
+ EXPECT_FALSE(value.ok());
+ EXPECT_EQ(ENOENT, value.error().code());
+
+ uid = TEST_UID3;
+ EXPECT_EQ(-ENOENT, mTc.changeUidOwnerRule(chain, uid, ALLOW, DENYLIST));
+ value = mFakeUidOwnerMap.readValue(uid);
+ EXPECT_FALSE(value.ok());
+ EXPECT_EQ(ENOENT, value.error().code());
+ }
+
+ void checkEachUidValue(const std::vector<int32_t>& uids, UidOwnerMatchType match) {
+ for (uint32_t uid : uids) {
+ Result<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
+ EXPECT_RESULT_OK(value);
+ EXPECT_TRUE(value.value().rule & match);
+ }
+ std::set<uint32_t> uidSet(uids.begin(), uids.end());
+ const auto checkNoOtherUid = [&uidSet](const int32_t& key,
+ const BpfMap<uint32_t, UidOwnerValue>&) {
+ EXPECT_NE(uidSet.end(), uidSet.find(key));
+ return Result<void>();
+ };
+ EXPECT_RESULT_OK(mFakeUidOwnerMap.iterate(checkNoOtherUid));
+ }
+
+ void checkUidMapReplace(const std::string& name, const std::vector<int32_t>& uids,
+ UidOwnerMatchType match) {
+ bool isAllowlist = true;
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isAllowlist, uids));
+ checkEachUidValue(uids, match);
+
+ isAllowlist = false;
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isAllowlist, uids));
+ checkEachUidValue(uids, match);
+ }
+
+ void expectUidOwnerMapValues(const std::vector<uint32_t>& appUids, uint8_t expectedRule,
+ uint32_t expectedIif) {
+ for (uint32_t uid : appUids) {
+ Result<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
+ EXPECT_RESULT_OK(value);
+ EXPECT_EQ(expectedRule, value.value().rule)
+ << "Expected rule for UID " << uid << " to be " << expectedRule << ", but was "
+ << value.value().rule;
+ EXPECT_EQ(expectedIif, value.value().iif)
+ << "Expected iif for UID " << uid << " to be " << expectedIif << ", but was "
+ << value.value().iif;
+ }
+ }
+
+ template <class Key, class Value>
+ void expectMapEmpty(BpfMap<Key, Value>& map) {
+ auto isEmpty = map.isEmpty();
+ EXPECT_RESULT_OK(isEmpty);
+ EXPECT_TRUE(isEmpty.value());
+ }
+
+ void expectUidPermissionMapValues(const std::vector<uid_t>& appUids, uint8_t expectedValue) {
+ for (uid_t uid : appUids) {
+ Result<uint8_t> value = mFakeUidPermissionMap.readValue(uid);
+ EXPECT_RESULT_OK(value);
+ EXPECT_EQ(expectedValue, value.value())
+ << "Expected value for UID " << uid << " to be " << expectedValue
+ << ", but was " << value.value();
+ }
+ }
+
+ void expectPrivilegedUserSet(const std::vector<uid_t>& appUids) {
+ std::lock_guard guard(mTc.mMutex);
+ EXPECT_EQ(appUids.size(), mTc.mPrivilegedUser.size());
+ for (uid_t uid : appUids) {
+ EXPECT_NE(mTc.mPrivilegedUser.end(), mTc.mPrivilegedUser.find(uid));
+ }
+ }
+
+ void expectPrivilegedUserSetEmpty() {
+ std::lock_guard guard(mTc.mMutex);
+ EXPECT_TRUE(mTc.mPrivilegedUser.empty());
+ }
+
+ void addPrivilegedUid(uid_t uid) {
+ std::vector privilegedUid = {uid};
+ mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, privilegedUid);
+ }
+
+ void removePrivilegedUid(uid_t uid) {
+ std::vector privilegedUid = {uid};
+ mTc.setPermissionForUids(INetd::PERMISSION_NONE, privilegedUid);
+ }
+
+ void expectFakeStatsUnchanged(uint64_t cookie, uint32_t tag, uint32_t uid,
+ StatsKey tagStatsMapKey) {
+ Result<UidTagValue> cookieMapResult = mFakeCookieTagMap.readValue(cookie);
+ EXPECT_RESULT_OK(cookieMapResult);
+ EXPECT_EQ(uid, cookieMapResult.value().uid);
+ EXPECT_EQ(tag, cookieMapResult.value().tag);
+ Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
+ EXPECT_RESULT_OK(counterSetResult);
+ EXPECT_EQ(TEST_COUNTERSET, counterSetResult.value());
+ Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
+ EXPECT_RESULT_OK(statsMapResult);
+ EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
+ EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+ tagStatsMapKey.tag = 0;
+ statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
+ EXPECT_RESULT_OK(statsMapResult);
+ EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
+ EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+ auto appStatsResult = mFakeAppUidStatsMap.readValue(uid);
+ EXPECT_RESULT_OK(appStatsResult);
+ EXPECT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
+ EXPECT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
+ }
+
+ Status updateUidOwnerMaps(const std::vector<uint32_t>& appUids,
+ UidOwnerMatchType matchType, TrafficController::IptOp op) {
+ Status ret(0);
+ for (auto uid : appUids) {
+ ret = mTc.updateUidOwnerMap(uid, matchType, op);
+ if(!isOk(ret)) break;
+ }
+ return ret;
+ }
+
+};
+
+TEST_F(TrafficControllerTest, TestSetCounterSet) {
+ uid_t callingUid = TEST_UID2;
+ addPrivilegedUid(callingUid);
+ ASSERT_EQ(0, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, callingUid));
+ uid_t uid = TEST_UID;
+ Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
+ ASSERT_RESULT_OK(counterSetResult);
+ ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
+ ASSERT_EQ(0, mTc.setCounterSet(DEFAULT_COUNTERSET, TEST_UID, callingUid));
+ ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
+ expectMapEmpty(mFakeUidCounterSetMap);
+}
+
+TEST_F(TrafficControllerTest, TestSetCounterSetWithoutPermission) {
+ ASSERT_EQ(-EPERM, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, TEST_UID2));
+ uid_t uid = TEST_UID;
+ ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
+ expectMapEmpty(mFakeUidCounterSetMap);
+}
+
+TEST_F(TrafficControllerTest, TestSetInvalidCounterSet) {
+ uid_t callingUid = TEST_UID2;
+ addPrivilegedUid(callingUid);
+ ASSERT_GT(0, mTc.setCounterSet(OVERFLOW_COUNTERSET, TEST_UID, callingUid));
+ uid_t uid = TEST_UID;
+ ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
+ expectMapEmpty(mFakeUidCounterSetMap);
+}
+
+TEST_F(TrafficControllerTest, TestDeleteTagDataWithoutPermission) {
+ uint64_t cookie = 1;
+ uid_t uid = TEST_UID;
+ uint32_t tag = TEST_TAG;
+ StatsKey tagStatsMapKey;
+ populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
+ ASSERT_EQ(-EPERM, mTc.deleteTagData(0, TEST_UID, TEST_UID2));
+
+ expectFakeStatsUnchanged(cookie, tag, uid, tagStatsMapKey);
+}
+
+TEST_F(TrafficControllerTest, TestDeleteTagData) {
+ uid_t callingUid = TEST_UID2;
+ addPrivilegedUid(callingUid);
+ uint64_t cookie = 1;
+ uid_t uid = TEST_UID;
+ uint32_t tag = TEST_TAG;
+ StatsKey tagStatsMapKey;
+ populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
+ ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID, callingUid));
+ ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie).ok());
+ Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
+ ASSERT_RESULT_OK(counterSetResult);
+ ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
+ ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey).ok());
+ tagStatsMapKey.tag = 0;
+ Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
+ ASSERT_RESULT_OK(statsMapResult);
+ ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
+ ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+ auto appStatsResult = mFakeAppUidStatsMap.readValue(TEST_UID);
+ ASSERT_RESULT_OK(appStatsResult);
+ ASSERT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
+ ASSERT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
+}
+
+TEST_F(TrafficControllerTest, TestDeleteAllUidData) {
+ uid_t callingUid = TEST_UID2;
+ addPrivilegedUid(callingUid);
+ uint64_t cookie = 1;
+ uid_t uid = TEST_UID;
+ uint32_t tag = TEST_TAG;
+ StatsKey tagStatsMapKey;
+ populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
+ ASSERT_EQ(0, mTc.deleteTagData(0, TEST_UID, callingUid));
+ ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie).ok());
+ ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
+ ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey).ok());
+ tagStatsMapKey.tag = 0;
+ ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey).ok());
+ ASSERT_FALSE(mFakeAppUidStatsMap.readValue(TEST_UID).ok());
+}
+
+TEST_F(TrafficControllerTest, TestDeleteDataWithTwoTags) {
+ uid_t callingUid = TEST_UID2;
+ addPrivilegedUid(callingUid);
+ uint64_t cookie1 = 1;
+ uint64_t cookie2 = 2;
+ uid_t uid = TEST_UID;
+ uint32_t tag1 = TEST_TAG;
+ uint32_t tag2 = TEST_TAG + 1;
+ StatsKey tagStatsMapKey1;
+ StatsKey tagStatsMapKey2;
+ populateFakeStats(cookie1, uid, tag1, &tagStatsMapKey1);
+ populateFakeStats(cookie2, uid, tag2, &tagStatsMapKey2);
+ ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID, callingUid));
+ ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie1).ok());
+ Result<UidTagValue> cookieMapResult = mFakeCookieTagMap.readValue(cookie2);
+ ASSERT_RESULT_OK(cookieMapResult);
+ ASSERT_EQ(TEST_UID, cookieMapResult.value().uid);
+ ASSERT_EQ(TEST_TAG + 1, cookieMapResult.value().tag);
+ Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
+ ASSERT_RESULT_OK(counterSetResult);
+ ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
+ ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey1).ok());
+ Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey2);
+ ASSERT_RESULT_OK(statsMapResult);
+ ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
+ ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+}
+
+TEST_F(TrafficControllerTest, TestDeleteDataWithTwoUids) {
+ uid_t callingUid = TEST_UID2;
+ addPrivilegedUid(callingUid);
+ uint64_t cookie1 = 1;
+ uint64_t cookie2 = 2;
+ uid_t uid1 = TEST_UID;
+ uid_t uid2 = TEST_UID + 1;
+ uint32_t tag = TEST_TAG;
+ StatsKey tagStatsMapKey1;
+ StatsKey tagStatsMapKey2;
+ populateFakeStats(cookie1, uid1, tag, &tagStatsMapKey1);
+ populateFakeStats(cookie2, uid2, tag, &tagStatsMapKey2);
+
+ // Delete the stats of one of the uid. Check if it is properly collected by
+ // removedStats.
+ ASSERT_EQ(0, mTc.deleteTagData(0, uid2, callingUid));
+ ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie2).ok());
+ Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid1);
+ ASSERT_RESULT_OK(counterSetResult);
+ ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
+ ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid2).ok());
+ ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey2).ok());
+ tagStatsMapKey2.tag = 0;
+ ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey2).ok());
+ ASSERT_FALSE(mFakeAppUidStatsMap.readValue(uid2).ok());
+ tagStatsMapKey1.tag = 0;
+ Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey1);
+ ASSERT_RESULT_OK(statsMapResult);
+ ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
+ ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+ auto appStatsResult = mFakeAppUidStatsMap.readValue(uid1);
+ ASSERT_RESULT_OK(appStatsResult);
+ ASSERT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
+ ASSERT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
+
+ // Delete the stats of the other uid.
+ ASSERT_EQ(0, mTc.deleteTagData(0, uid1, callingUid));
+ ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey1).ok());
+ ASSERT_FALSE(mFakeAppUidStatsMap.readValue(uid1).ok());
+}
+
+TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
+ uint32_t uid = TEST_UID;
+ ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, DENY, DENYLIST)));
+ Result<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
+ ASSERT_RESULT_OK(value);
+ ASSERT_TRUE(value.value().rule & STANDBY_MATCH);
+
+ ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, ALLOW, ALLOWLIST)));
+ value = mFakeUidOwnerMap.readValue(uid);
+ ASSERT_RESULT_OK(value);
+ ASSERT_TRUE(value.value().rule & DOZABLE_MATCH);
+
+ ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, DENY, ALLOWLIST)));
+ value = mFakeUidOwnerMap.readValue(uid);
+ ASSERT_RESULT_OK(value);
+ ASSERT_FALSE(value.value().rule & DOZABLE_MATCH);
+
+ ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, DENYLIST)));
+ ASSERT_FALSE(mFakeUidOwnerMap.readValue(uid).ok());
+
+ uid = TEST_UID2;
+ ASSERT_FALSE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, DENYLIST)));
+ ASSERT_FALSE(mFakeUidOwnerMap.readValue(uid).ok());
+}
+
+TEST_F(TrafficControllerTest, TestChangeUidOwnerRule) {
+ checkUidOwnerRuleForChain(DOZABLE, DOZABLE_MATCH);
+ checkUidOwnerRuleForChain(STANDBY, STANDBY_MATCH);
+ checkUidOwnerRuleForChain(POWERSAVE, POWERSAVE_MATCH);
+ checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
+ ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST));
+ ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST));
+}
+
+TEST_F(TrafficControllerTest, TestReplaceUidOwnerMap) {
+ std::vector<int32_t> uids = {TEST_UID, TEST_UID2, TEST_UID3};
+ checkUidMapReplace("fw_dozable", uids, DOZABLE_MATCH);
+ checkUidMapReplace("fw_standby", uids, STANDBY_MATCH);
+ checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH);
+ checkUidMapReplace("fw_restricted", uids, RESTRICTED_MATCH);
+ ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids));
+}
+
+TEST_F(TrafficControllerTest, TestReplaceSameChain) {
+ std::vector<int32_t> uids = {TEST_UID, TEST_UID2, TEST_UID3};
+ checkUidMapReplace("fw_dozable", uids, DOZABLE_MATCH);
+ std::vector<int32_t> newUids = {TEST_UID2, TEST_UID3};
+ checkUidMapReplace("fw_dozable", newUids, DOZABLE_MATCH);
+}
+
+TEST_F(TrafficControllerTest, TestDenylistUidMatch) {
+ std::vector<uint32_t> appUids = {1000, 1001, 10012};
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
+ expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0);
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
+ expectMapEmpty(mFakeUidOwnerMap);
+}
+
+TEST_F(TrafficControllerTest, TestAllowlistUidMatch) {
+ std::vector<uint32_t> appUids = {1000, 1001, 10012};
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpInsert)));
+ expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0);
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpDelete)));
+ expectMapEmpty(mFakeUidOwnerMap);
+}
+
+TEST_F(TrafficControllerTest, TestReplaceMatchUid) {
+ std::vector<uint32_t> appUids = {1000, 1001, 10012};
+ // Add appUids to the denylist and expect that their values are all PENALTY_BOX_MATCH.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
+ expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0);
+
+ // Add the same UIDs to the allowlist and expect that we get PENALTY_BOX_MATCH |
+ // HAPPY_BOX_MATCH.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpInsert)));
+ expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH | PENALTY_BOX_MATCH, 0);
+
+ // Remove the same UIDs from the allowlist and check the PENALTY_BOX_MATCH is still there.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpDelete)));
+ expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0);
+
+ // Remove the same UIDs from the denylist and check the map is empty.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
+ ASSERT_FALSE(mFakeUidOwnerMap.getFirstKey().ok());
+}
+
+TEST_F(TrafficControllerTest, TestDeleteWrongMatchSilentlyFails) {
+ std::vector<uint32_t> appUids = {1000, 1001, 10012};
+ // If the uid does not exist in the map, trying to delete a rule about it will fail.
+ ASSERT_FALSE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
+ expectMapEmpty(mFakeUidOwnerMap);
+
+ // Add denylist rules for appUids.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
+ expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0);
+
+ // Delete (non-existent) denylist rules for appUids, and check that this silently does
+ // nothing if the uid is in the map but does not have denylist match. This is required because
+ // NetworkManagementService will try to remove a uid from denylist after adding it to the
+ // allowlist and if the remove fails it will not update the uid status.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
+ expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0);
+}
+
+TEST_F(TrafficControllerTest, TestAddUidInterfaceFilteringRules) {
+ int iif0 = 15;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001})));
+ expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif0);
+
+ // Add some non-overlapping new uids. They should coexist with existing rules
+ int iif1 = 16;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001})));
+ expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif0);
+ expectUidOwnerMapValues({2000, 2001}, IIF_MATCH, iif1);
+
+ // Overwrite some existing uids
+ int iif2 = 17;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif2, {1000, 2000})));
+ expectUidOwnerMapValues({1001}, IIF_MATCH, iif0);
+ expectUidOwnerMapValues({2001}, IIF_MATCH, iif1);
+ expectUidOwnerMapValues({1000, 2000}, IIF_MATCH, iif2);
+}
+
+TEST_F(TrafficControllerTest, TestRemoveUidInterfaceFilteringRules) {
+ int iif0 = 15;
+ int iif1 = 16;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001})));
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001})));
+ expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif0);
+ expectUidOwnerMapValues({2000, 2001}, IIF_MATCH, iif1);
+
+ // Rmove some uids
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1001, 2001})));
+ expectUidOwnerMapValues({1000}, IIF_MATCH, iif0);
+ expectUidOwnerMapValues({2000}, IIF_MATCH, iif1);
+ checkEachUidValue({1000, 2000}, IIF_MATCH); // Make sure there are only two uids remaining
+
+ // Remove non-existent uids shouldn't fail
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({2000, 3000})));
+ expectUidOwnerMapValues({1000}, IIF_MATCH, iif0);
+ checkEachUidValue({1000}, IIF_MATCH); // Make sure there are only one uid remaining
+
+ // Remove everything
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000})));
+ expectMapEmpty(mFakeUidOwnerMap);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithExistingMatches) {
+ // Set up existing PENALTY_BOX_MATCH rules
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000, 1001, 10012}, PENALTY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
+ expectUidOwnerMapValues({1000, 1001, 10012}, PENALTY_BOX_MATCH, 0);
+
+ // Add some partially-overlapping uid owner rules and check result
+ int iif1 = 32;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10012, 10013, 10014})));
+ expectUidOwnerMapValues({1000, 1001}, PENALTY_BOX_MATCH, 0);
+ expectUidOwnerMapValues({10012}, PENALTY_BOX_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({10013, 10014}, IIF_MATCH, iif1);
+
+ // Removing some PENALTY_BOX_MATCH rules should not change uid interface rule
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1001, 10012}, PENALTY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
+ expectUidOwnerMapValues({1000}, PENALTY_BOX_MATCH, 0);
+ expectUidOwnerMapValues({10012, 10013, 10014}, IIF_MATCH, iif1);
+
+ // Remove all uid interface rules
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({10012, 10013, 10014})));
+ expectUidOwnerMapValues({1000}, PENALTY_BOX_MATCH, 0);
+ // Make sure these are the only uids left
+ checkEachUidValue({1000}, PENALTY_BOX_MATCH);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithNewMatches) {
+ int iif1 = 56;
+ // Set up existing uid interface rules
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10001, 10002})));
+ expectUidOwnerMapValues({10001, 10002}, IIF_MATCH, iif1);
+
+ // Add some partially-overlapping doze rules
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {10002, 10003}));
+ expectUidOwnerMapValues({10001}, IIF_MATCH, iif1);
+ expectUidOwnerMapValues({10002}, DOZABLE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({10003}, DOZABLE_MATCH, 0);
+
+ // Introduce a third rule type (powersave) on various existing UIDs
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {10000, 10001, 10002, 10003}));
+ expectUidOwnerMapValues({10000}, POWERSAVE_MATCH, 0);
+ expectUidOwnerMapValues({10001}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({10002}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({10003}, POWERSAVE_MATCH | DOZABLE_MATCH, 0);
+
+ // Remove all doze rules
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {}));
+ expectUidOwnerMapValues({10000}, POWERSAVE_MATCH, 0);
+ expectUidOwnerMapValues({10001}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({10002}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({10003}, POWERSAVE_MATCH, 0);
+
+ // Remove all powersave rules, expect ownerMap to only have uid interface rules left
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {}));
+ expectUidOwnerMapValues({10001, 10002}, IIF_MATCH, iif1);
+ // Make sure these are the only uids left
+ checkEachUidValue({10001, 10002}, IIF_MATCH);
+}
+
+TEST_F(TrafficControllerTest, TestGrantInternetPermission) {
+ std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+ mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, appUids);
+ expectMapEmpty(mFakeUidPermissionMap);
+ expectPrivilegedUserSetEmpty();
+}
+
+TEST_F(TrafficControllerTest, TestRevokeInternetPermission) {
+ std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+ mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+ expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE);
+}
+
+TEST_F(TrafficControllerTest, TestPermissionUninstalled) {
+ std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+ mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
+ expectUidPermissionMapValues(appUids, INetd::PERMISSION_UPDATE_DEVICE_STATS);
+ expectPrivilegedUserSet(appUids);
+
+ std::vector<uid_t> uidToRemove = {TEST_UID};
+ mTc.setPermissionForUids(INetd::PERMISSION_UNINSTALLED, uidToRemove);
+
+ std::vector<uid_t> uidRemain = {TEST_UID3, TEST_UID2};
+ expectUidPermissionMapValues(uidRemain, INetd::PERMISSION_UPDATE_DEVICE_STATS);
+ expectPrivilegedUserSet(uidRemain);
+
+ mTc.setPermissionForUids(INetd::PERMISSION_UNINSTALLED, uidRemain);
+ expectMapEmpty(mFakeUidPermissionMap);
+ expectPrivilegedUserSetEmpty();
+}
+
+TEST_F(TrafficControllerTest, TestGrantUpdateStatsPermission) {
+ std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+ mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
+ expectUidPermissionMapValues(appUids, INetd::PERMISSION_UPDATE_DEVICE_STATS);
+ expectPrivilegedUserSet(appUids);
+
+ mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+ expectPrivilegedUserSetEmpty();
+ expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE);
+}
+
+TEST_F(TrafficControllerTest, TestRevokeUpdateStatsPermission) {
+ std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+ mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
+ expectPrivilegedUserSet(appUids);
+
+ std::vector<uid_t> uidToRemove = {TEST_UID};
+ mTc.setPermissionForUids(INetd::PERMISSION_NONE, uidToRemove);
+
+ std::vector<uid_t> uidRemain = {TEST_UID3, TEST_UID2};
+ expectPrivilegedUserSet(uidRemain);
+
+ mTc.setPermissionForUids(INetd::PERMISSION_NONE, uidRemain);
+ expectPrivilegedUserSetEmpty();
+}
+
+TEST_F(TrafficControllerTest, TestGrantWrongPermission) {
+ std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+ mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+ expectPrivilegedUserSetEmpty();
+ expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE);
+}
+
+TEST_F(TrafficControllerTest, TestGrantDuplicatePermissionSlientlyFail) {
+ std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
+
+ mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, appUids);
+ expectMapEmpty(mFakeUidPermissionMap);
+
+ std::vector<uid_t> uidToAdd = {TEST_UID};
+ mTc.setPermissionForUids(INetd::PERMISSION_INTERNET, uidToAdd);
+
+ expectPrivilegedUserSetEmpty();
+
+ mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+ expectUidPermissionMapValues(appUids, INetd::PERMISSION_NONE);
+
+ mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, appUids);
+ expectPrivilegedUserSet(appUids);
+
+ mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, uidToAdd);
+ expectPrivilegedUserSet(appUids);
+
+ mTc.setPermissionForUids(INetd::PERMISSION_NONE, appUids);
+ expectPrivilegedUserSetEmpty();
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/include/Common.h b/service/native/include/Common.h
new file mode 100644
index 0000000..7c0b797
--- /dev/null
+++ b/service/native/include/Common.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#pragma once
+// TODO: deduplicate with the constants in NetdConstants.h.
+#include <aidl/android/net/INetd.h>
+
+using aidl::android::net::INetd;
+
+enum FirewallRule { ALLOW = INetd::FIREWALL_RULE_ALLOW, DENY = INetd::FIREWALL_RULE_DENY };
+
+// ALLOWLIST means the firewall denies all by default, uids must be explicitly ALLOWed
+// DENYLIST means the firewall allows all by default, uids must be explicitly DENYed
+
+enum FirewallType { ALLOWLIST = INetd::FIREWALL_ALLOWLIST, DENYLIST = INetd::FIREWALL_DENYLIST };
+
+enum ChildChain {
+ NONE = INetd::FIREWALL_CHAIN_NONE,
+ DOZABLE = INetd::FIREWALL_CHAIN_DOZABLE,
+ STANDBY = INetd::FIREWALL_CHAIN_STANDBY,
+ POWERSAVE = INetd::FIREWALL_CHAIN_POWERSAVE,
+ RESTRICTED = INetd::FIREWALL_CHAIN_RESTRICTED,
+ INVALID_CHAIN
+};
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
new file mode 100644
index 0000000..c050871
--- /dev/null
+++ b/service/native/include/TrafficController.h
@@ -0,0 +1,203 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <set>
+#include <Common.h>
+
+#include "android-base/thread_annotations.h"
+#include "bpf/BpfMap.h"
+#include "bpf_shared.h"
+#include "netdutils/DumpWriter.h"
+#include "netdutils/NetlinkListener.h"
+#include "netdutils/StatusOr.h"
+#include "utils/String16.h"
+
+namespace android {
+namespace net {
+
+using netdutils::StatusOr;
+
+class TrafficController {
+ public:
+ /*
+ * Initialize the whole controller
+ */
+ netdutils::Status start();
+
+ int setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
+
+ /*
+ * When deleting a tag data, the qtaguid module will grab the spinlock of each
+ * related rb_tree one by one and delete the tag information, counterSet
+ * information, iface stats information and uid stats information one by one.
+ * The new eBPF implementation is done similiarly by removing the entry on
+ * each map one by one. And deleting processes are also protected by the
+ * spinlock of the map. So no additional lock is required.
+ */
+ int deleteTagData(uint32_t tag, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
+
+ /*
+ * Swap the stats map config from current active stats map to the idle one.
+ */
+ netdutils::Status swapActiveStatsMap() EXCLUDES(mMutex);
+
+ /*
+ * Add the interface name and index pair into the eBPF map.
+ */
+ int addInterface(const char* name, uint32_t ifaceIndex);
+
+ int changeUidOwnerRule(ChildChain chain, const uid_t uid, FirewallRule rule, FirewallType type);
+
+ int removeUidOwnerRule(const uid_t uid);
+
+ int replaceUidOwnerMap(const std::string& name, bool isAllowlist,
+ const std::vector<int32_t>& uids);
+
+ enum IptOp { IptOpInsert, IptOpDelete };
+
+ netdutils::Status updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
+ FirewallType type) EXCLUDES(mMutex);
+
+ void dump(netdutils::DumpWriter& dw, bool verbose) EXCLUDES(mMutex);
+
+ netdutils::Status replaceRulesInMap(UidOwnerMatchType match, const std::vector<int32_t>& uids)
+ EXCLUDES(mMutex);
+
+ netdutils::Status addUidInterfaceRules(const int ifIndex, const std::vector<int32_t>& uids)
+ EXCLUDES(mMutex);
+ netdutils::Status removeUidInterfaceRules(const std::vector<int32_t>& uids) EXCLUDES(mMutex);
+
+ netdutils::Status updateUidOwnerMap(const uint32_t uid,
+ UidOwnerMatchType matchType, IptOp op) EXCLUDES(mMutex);
+ static const String16 DUMP_KEYWORD;
+
+ int toggleUidOwnerMap(ChildChain chain, bool enable) EXCLUDES(mMutex);
+
+ static netdutils::StatusOr<std::unique_ptr<netdutils::NetlinkListenerInterface>>
+ makeSkDestroyListener();
+
+ void setPermissionForUids(int permission, const std::vector<uid_t>& uids) EXCLUDES(mMutex);
+
+ FirewallType getFirewallType(ChildChain);
+
+ static const char* LOCAL_DOZABLE;
+ static const char* LOCAL_STANDBY;
+ static const char* LOCAL_POWERSAVE;
+ static const char* LOCAL_RESTRICTED;
+
+ private:
+ /*
+ * mCookieTagMap: Store the corresponding tag and uid for a specific socket.
+ * DO NOT hold any locks when modifying this map, otherwise when the untag
+ * operation is waiting for a lock hold by other process and there are more
+ * sockets being closed than can fit in the socket buffer of the netlink socket
+ * that receives them, then the kernel will drop some of these sockets and we
+ * won't delete their tags.
+ * Map Key: uint64_t socket cookie
+ * Map Value: UidTagValue, contains a uint32 uid and a uint32 tag.
+ */
+ bpf::BpfMap<uint64_t, UidTagValue> mCookieTagMap GUARDED_BY(mMutex);
+
+ /*
+ * mUidCounterSetMap: Store the counterSet of a specific uid.
+ * Map Key: uint32 uid.
+ * Map Value: uint32 counterSet specifies if the traffic is a background
+ * or foreground traffic.
+ */
+ bpf::BpfMap<uint32_t, uint8_t> mUidCounterSetMap GUARDED_BY(mMutex);
+
+ /*
+ * mAppUidStatsMap: Store the total traffic stats for a uid regardless of
+ * tag, counterSet and iface. The stats is used by TrafficStats.getUidStats
+ * API to return persistent stats for a specific uid since device boot.
+ */
+ bpf::BpfMap<uint32_t, StatsValue> mAppUidStatsMap;
+
+ /*
+ * mStatsMapA/mStatsMapB: Store the traffic statistics for a specific
+ * combination of uid, tag, iface and counterSet. These two maps contain
+ * both tagged and untagged traffic.
+ * Map Key: StatsKey contains the uid, tag, counterSet and ifaceIndex
+ * information.
+ * Map Value: Stats, contains packet count and byte count of each
+ * transport protocol on egress and ingress direction.
+ */
+ bpf::BpfMap<StatsKey, StatsValue> mStatsMapA GUARDED_BY(mMutex);
+
+ bpf::BpfMap<StatsKey, StatsValue> mStatsMapB GUARDED_BY(mMutex);
+
+ /*
+ * mIfaceIndexNameMap: Store the index name pair of each interface show up
+ * on the device since boot. The interface index is used by the eBPF program
+ * to correctly match the iface name when receiving a packet.
+ */
+ bpf::BpfMap<uint32_t, IfaceValue> mIfaceIndexNameMap;
+
+ /*
+ * mIfaceStataMap: Store per iface traffic stats gathered from xt_bpf
+ * filter.
+ */
+ bpf::BpfMap<uint32_t, StatsValue> mIfaceStatsMap;
+
+ /*
+ * mConfigurationMap: Store the current network policy about uid filtering
+ * and the current stats map in use. There are two configuration entries in
+ * the map right now:
+ * - Entry with UID_RULES_CONFIGURATION_KEY:
+ * Store the configuration for the current uid rules. It indicates the device
+ * is in doze/powersave/standby/restricted mode.
+ * - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY:
+ * Stores the current live stats map that kernel program is writing to.
+ * Userspace can do scraping and cleaning job on the other one depending on the
+ * current configs.
+ */
+ bpf::BpfMap<uint32_t, uint8_t> mConfigurationMap GUARDED_BY(mMutex);
+
+ /*
+ * mUidOwnerMap: Store uids that are used for bandwidth control uid match.
+ */
+ bpf::BpfMap<uint32_t, UidOwnerValue> mUidOwnerMap GUARDED_BY(mMutex);
+
+ /*
+ * mUidOwnerMap: Store uids that are used for INTERNET permission check.
+ */
+ bpf::BpfMap<uint32_t, uint8_t> mUidPermissionMap GUARDED_BY(mMutex);
+
+ std::unique_ptr<netdutils::NetlinkListenerInterface> mSkDestroyListener;
+
+ netdutils::Status removeRule(uint32_t uid, UidOwnerMatchType match) REQUIRES(mMutex);
+
+ netdutils::Status addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif = 0)
+ REQUIRES(mMutex);
+
+ 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);
+
+ bool hasUpdateDeviceStatsPermission(uid_t uid) REQUIRES(mMutex);
+
+ // For testing
+ friend class TrafficControllerTest;
+};
+
+} // namespace net
+} // namespace android
diff --git a/service/native/jni/com_android_server_BpfNetMaps.cpp b/service/native/jni/com_android_server_BpfNetMaps.cpp
new file mode 100644
index 0000000..7ab4d46
--- /dev/null
+++ b/service/native/jni/com_android_server_BpfNetMaps.cpp
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TrafficControllerJni"
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <net/if.h>
+#include <vector>
+
+#include "TrafficController.h"
+#include "android-base/logging.h"
+#include "bpf_shared.h"
+#include "utils/Log.h"
+
+using android::net::TrafficController;
+using android::netdutils::Status;
+
+using UidOwnerMatchType::PENALTY_BOX_MATCH;
+using UidOwnerMatchType::HAPPY_BOX_MATCH;
+
+static android::net::TrafficController mTc;
+
+namespace android {
+
+static void native_init(JNIEnv* env, jobject clazz) {
+ Status status = mTc.start();
+ if (!isOk(status)) {
+ ALOGE("%s failed", __func__);
+ }
+}
+
+static jint native_addNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+ const uint32_t appUids = static_cast<uint32_t>(abs(uid));
+ Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOp::IptOpInsert);
+ if (!isOk(status)) {
+ ALOGE("%s failed, errer code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_removeNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+ const uint32_t appUids = static_cast<uint32_t>(abs(uid));
+ Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOp::IptOpDelete);
+ if (!isOk(status)) {
+ ALOGE("%s failed, errer code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_addNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+ const uint32_t appUids = static_cast<uint32_t>(abs(uid));
+ Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
+ TrafficController::IptOp::IptOpInsert);
+ if (!isOk(status)) {
+ ALOGE("%s failed, errer code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_removeNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+ const uint32_t appUids = static_cast<uint32_t>(abs(uid));
+ Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
+ TrafficController::IptOp::IptOpDelete);
+ if (!isOk(status)) {
+ ALOGD("%s failed, errer code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_setChildChain(JNIEnv* env, jobject clazz, jint childChain, jboolean enable) {
+ auto chain = static_cast<ChildChain>(childChain);
+ int res = mTc.toggleUidOwnerMap(chain, enable);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+static jint native_replaceUidChain(JNIEnv* env, jobject clazz, jstring name, jboolean isAllowlist,
+ jintArray jUids) {
+ const ScopedUtfChars chainNameUtf8(env, name);
+ if (chainNameUtf8.c_str() == nullptr) {
+ return -EINVAL;
+ }
+ const std::string chainName(chainNameUtf8.c_str());
+
+ ScopedIntArrayRO uids(env, jUids);
+ if (uids.get() == nullptr) {
+ return -EINVAL;
+ }
+
+ size_t size = uids.size();
+ std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
+ int res = mTc.replaceUidOwnerMap(chainName, isAllowlist, data);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+static FirewallType getFirewallType(ChildChain chain) {
+ switch (chain) {
+ case DOZABLE:
+ return ALLOWLIST;
+ case STANDBY:
+ return DENYLIST;
+ case POWERSAVE:
+ return ALLOWLIST;
+ case RESTRICTED:
+ return ALLOWLIST;
+ case NONE:
+ default:
+ return DENYLIST;
+ }
+}
+
+static jint native_setUidRule(JNIEnv* env, jobject clazz, jint childChain, jint uid,
+ jint firewallRule) {
+ auto chain = static_cast<ChildChain>(childChain);
+ auto rule = static_cast<FirewallRule>(firewallRule);
+ FirewallType fType = getFirewallType(chain);
+
+ int res = mTc.changeUidOwnerRule(chain, uid, rule, fType);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+static jint native_addUidInterfaceRules(JNIEnv* env, jobject clazz, jstring ifName,
+ jintArray jUids) {
+ const ScopedUtfChars ifNameUtf8(env, ifName);
+ if (ifNameUtf8.c_str() == nullptr) {
+ return -EINVAL;
+ }
+ const std::string interfaceName(ifNameUtf8.c_str());
+ const int ifIndex = if_nametoindex(interfaceName.c_str());
+
+ ScopedIntArrayRO uids(env, jUids);
+ if (uids.get() == nullptr) {
+ return -EINVAL;
+ }
+
+ size_t size = uids.size();
+ std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
+ Status status = mTc.addUidInterfaceRules(ifIndex, data);
+ if (!isOk(status)) {
+ ALOGE("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_removeUidInterfaceRules(JNIEnv* env, jobject clazz, jintArray jUids) {
+ ScopedIntArrayRO uids(env, jUids);
+ if (uids.get() == nullptr) {
+ return -EINVAL;
+ }
+
+ size_t size = uids.size();
+ std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
+ Status status = mTc.removeUidInterfaceRules(data);
+ if (!isOk(status)) {
+ ALOGE("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_swapActiveStatsMap(JNIEnv* env, jobject clazz) {
+ Status status = mTc.swapActiveStatsMap();
+ if (!isOk(status)) {
+ ALOGD("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static void native_setPermissionForUids(JNIEnv* env, jobject clazz, jint permission,
+ jintArray jUids) {
+ ScopedIntArrayRO uids(env, jUids);
+ if (uids.get() == nullptr) return;
+
+ size_t size = uids.size();
+ static_assert(sizeof(*(uids.get())) == sizeof(uid_t));
+ std::vector<uid_t> data ((uid_t *)&uids[0], (uid_t*)&uids[size]);
+ mTc.setPermissionForUids(permission, data);
+}
+
+static jint native_setCounterSet(JNIEnv* env, jobject clazz, jint setNum, jint uid) {
+ uid_t callingUid = getuid();
+ int res = mTc.setCounterSet(setNum, (uid_t)uid, callingUid);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+static jint native_deleteTagData(JNIEnv* env, jobject clazz, jint tagNum, jint uid) {
+ uid_t callingUid = getuid();
+ int res = mTc.deleteTagData(tagNum, (uid_t)uid, callingUid);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+/*
+ * JNI registration.
+ */
+// clang-format off
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"native_init", "()V",
+ (void*)native_init},
+ {"native_addNaughtyApp", "(I)I",
+ (void*)native_addNaughtyApp},
+ {"native_removeNaughtyApp", "(I)I",
+ (void*)native_removeNaughtyApp},
+ {"native_addNiceApp", "(I)I",
+ (void*)native_addNiceApp},
+ {"native_removeNiceApp", "(I)I",
+ (void*)native_removeNiceApp},
+ {"native_setChildChain", "(IZ)I",
+ (void*)native_setChildChain},
+ {"native_replaceUidChain", "(Ljava/lang/String;Z[I)I",
+ (void*)native_replaceUidChain},
+ {"native_setUidRule", "(III)I",
+ (void*)native_setUidRule},
+ {"native_addUidInterfaceRules", "(Ljava/lang/String;[I)I",
+ (void*)native_addUidInterfaceRules},
+ {"native_removeUidInterfaceRules", "([I)I",
+ (void*)native_removeUidInterfaceRules},
+ {"native_swapActiveStatsMap", "()I",
+ (void*)native_swapActiveStatsMap},
+ {"native_setPermissionForUids", "(I[I)V",
+ (void*)native_setPermissionForUids},
+ {"native_setCounterSet", "(II)I",
+ (void*)native_setCounterSet},
+ {"native_deleteTagData", "(II)I",
+ (void*)native_deleteTagData},
+};
+// clang-format on
+
+int register_com_android_server_BpfNetMaps(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "com/android/server/BpfNetMaps",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/service/native/jni/onload.cpp b/service/native/jni/onload.cpp
new file mode 100644
index 0000000..df7c77b
--- /dev/null
+++ b/service/native/jni/onload.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TrafficControllerJni"
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "utils/Log.h"
+
+namespace android {
+
+int register_com_android_server_BpfNetMaps(JNIEnv* env);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ ALOGE("%s: ERROR: GetEnv failed", __func__);
+ return JNI_ERR;
+ }
+
+ if (register_com_android_server_BpfNetMaps(env) < 0)
+ return JNI_ERR;
+
+ return JNI_VERSION_1_6;
+}
+
+}; // namespace android
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
new file mode 100644
index 0000000..5e208d8
--- /dev/null
+++ b/service/native/libs/libclat/Android.bp
@@ -0,0 +1,70 @@
+// 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_library_static {
+ name: "libclat",
+ defaults: ["netd_defaults"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ "bpf_headers",
+ "bpf_syscall_wrappers",
+ ],
+ srcs: [
+ "TcUtils.cpp", // TODO: move to frameworks/libs/net
+ "bpfhelper.cpp",
+ "clatutils.cpp",
+ ],
+ stl: "libc++_static",
+ static_libs: [
+ "libbase",
+ "libip_checksum",
+ "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
+ ],
+ shared_libs: ["liblog"],
+ export_include_dirs: ["include"],
+ min_sdk_version: "30",
+ apex_available: ["com.android.tethering"],
+}
+
+cc_test {
+ name: "libclat_test",
+ defaults: ["netd_defaults"],
+ test_suites: ["device-tests"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ "bpf_headers",
+ "bpf_syscall_wrappers",
+ ],
+ srcs: [
+ "TcUtilsTest.cpp",
+ "clatutils_test.cpp",
+ ],
+ static_libs: [
+ "libbase",
+ "libclat",
+ "libip_checksum",
+ "libnetd_test_tun_interface",
+ "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
+ "libtcutils",
+ ],
+ shared_libs: [
+ "liblog",
+ "libnetutils",
+ ],
+ require_root: true,
+}
diff --git a/service/native/libs/libclat/TcUtils.cpp b/service/native/libs/libclat/TcUtils.cpp
new file mode 100644
index 0000000..cdfb763
--- /dev/null
+++ b/service/native/libs/libclat/TcUtils.cpp
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TcUtils"
+
+#include "libclat/TcUtils.h"
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/netlink.h>
+#include <linux/pkt_cls.h>
+#include <linux/pkt_sched.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <log/log.h>
+
+#include "android-base/unique_fd.h"
+
+namespace android {
+namespace net {
+
+using std::max;
+
+// Sync from system/netd/server/NetlinkCommands.h
+const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
+const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
+
+static int doSIOCGIF(const std::string& interface, int opt) {
+ base::unique_fd ufd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+
+ if (ufd < 0) {
+ const int err = errno;
+ ALOGE("socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)");
+ return -err;
+ };
+
+ struct ifreq ifr = {};
+ // We use strncpy() instead of strlcpy() since kernel has to be able
+ // to handle non-zero terminated junk passed in by userspace anyway,
+ // and this way too long interface names (more than IFNAMSIZ-1 = 15
+ // characters plus terminating NULL) will not get truncated to 15
+ // characters and zero-terminated and thus potentially erroneously
+ // match a truncated interface if one were to exist.
+ strncpy(ifr.ifr_name, interface.c_str(), sizeof(ifr.ifr_name));
+
+ if (ioctl(ufd, opt, &ifr, sizeof(ifr))) return -errno;
+
+ if (opt == SIOCGIFHWADDR) return ifr.ifr_hwaddr.sa_family;
+ if (opt == SIOCGIFMTU) return ifr.ifr_mtu;
+ return -EINVAL;
+}
+
+int hardwareAddressType(const std::string& interface) {
+ return doSIOCGIF(interface, SIOCGIFHWADDR);
+}
+
+int deviceMTU(const std::string& interface) {
+ return doSIOCGIF(interface, SIOCGIFMTU);
+}
+
+base::Result<bool> isEthernet(const std::string& interface) {
+ int rv = hardwareAddressType(interface);
+ if (rv < 0) {
+ errno = -rv;
+ return ErrnoErrorf("Get hardware address type of interface {} failed", interface);
+ }
+
+ switch (rv) {
+ case ARPHRD_ETHER:
+ return true;
+ case ARPHRD_NONE:
+ case ARPHRD_RAWIP: // in Linux 4.14+ rmnet support was upstreamed and this is 519
+ case 530: // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
+ return false;
+ default:
+ errno = EAFNOSUPPORT; // Address family not supported
+ return ErrnoErrorf("Unknown hardware address type {} on interface {}", rv, interface);
+ }
+}
+
+// TODO: use //system/netd/server/NetlinkCommands.cpp:openNetlinkSocket(protocol)
+// and //system/netd/server/SockDiag.cpp:checkError(fd)
+static int sendAndProcessNetlinkResponse(const void* req, int len) {
+ base::unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
+ if (fd == -1) {
+ const int err = errno;
+ ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)");
+ return -err;
+ }
+
+ static constexpr int on = 1;
+ int rv = setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on));
+ if (rv) ALOGE("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, %d)", on);
+
+ // this is needed to get sane strace netlink parsing, it allocates the pid
+ rv = bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
+ if (rv) {
+ const int err = errno;
+ ALOGE("bind(fd, {AF_NETLINK, 0, 0})");
+ return -err;
+ }
+
+ // we do not want to receive messages from anyone besides the kernel
+ rv = connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
+ if (rv) {
+ const int err = errno;
+ ALOGE("connect(fd, {AF_NETLINK, 0, 0})");
+ return -err;
+ }
+
+ rv = send(fd, req, len, 0);
+ if (rv == -1) return -errno;
+ if (rv != len) return -EMSGSIZE;
+
+ struct {
+ nlmsghdr h;
+ nlmsgerr e;
+ char buf[256];
+ } resp = {};
+
+ rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
+
+ if (rv == -1) {
+ const int err = errno;
+ ALOGE("recv() failed");
+ return -err;
+ }
+
+ if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
+ ALOGE("recv() returned short packet: %d", rv);
+ return -EMSGSIZE;
+ }
+
+ if (resp.h.nlmsg_len != (unsigned)rv) {
+ ALOGE("recv() returned invalid header length: %d != %d", resp.h.nlmsg_len, rv);
+ return -EBADMSG;
+ }
+
+ if (resp.h.nlmsg_type != NLMSG_ERROR) {
+ ALOGE("recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
+ return -EBADMSG;
+ }
+
+ return resp.e.error; // returns 0 on success
+}
+
+// ADD: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_EXCL|NLM_F_CREATE
+// REPLACE: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_CREATE|NLM_F_REPLACE
+// DEL: nlMsgType=RTM_DELQDISC nlMsgFlags=0
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags) {
+ // This is the name of the qdisc we are attaching.
+ // Some hoop jumping to make this compile time constant with known size,
+ // so that the structure declaration is well defined at compile time.
+#define CLSACT "clsact"
+ // sizeof() includes the terminating NULL
+ static constexpr size_t ASCIIZ_LEN_CLSACT = sizeof(CLSACT);
+
+ const struct {
+ nlmsghdr n;
+ tcmsg t;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(ASCIIZ_LEN_CLSACT)];
+ } kind;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = nlMsgType,
+ .nlmsg_flags = static_cast<__u16>(NETLINK_REQUEST_FLAGS | nlMsgFlags),
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0),
+ .tcm_parent = TC_H_CLSACT,
+ },
+ .kind =
+ {
+ .attr =
+ {
+ .nla_len = NLA_HDRLEN + ASCIIZ_LEN_CLSACT,
+ .nla_type = TCA_KIND,
+ },
+ .str = CLSACT,
+ },
+ };
+#undef CLSACT
+
+ return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// direct-action
+int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet) {
+ // This is the name of the filter we're attaching (ie. this is the 'bpf'
+ // packet classifier enabled by kernel config option CONFIG_NET_CLS_BPF.
+ //
+ // We go through some hoops in order to make this compile time constants
+ // so that we can define the struct further down the function with the
+ // field for this sized correctly already during the build.
+#define BPF "bpf"
+ // sizeof() includes the terminating NULL
+ static constexpr size_t ASCIIZ_LEN_BPF = sizeof(BPF);
+
+ // This is to replicate program name suffix used by 'tc' Linux cli
+ // when it attaches programs.
+#define FSOBJ_SUFFIX ":[*fsobj]"
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_ingress6_clat_rawip:[*fsobj]
+ // and is the name of the pinned ingress ebpf program for ARPHRD_RAWIP interfaces.
+ // (also compatible with anything that has 0 size L2 header)
+ static constexpr char name_clat_rx_rawip[] = CLAT_INGRESS6_PROG_RAWIP_NAME FSOBJ_SUFFIX;
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_ingress6_clat_ether:[*fsobj]
+ // and is the name of the pinned ingress ebpf program for ARPHRD_ETHER interfaces.
+ // (also compatible with anything that has standard ethernet header)
+ static constexpr char name_clat_rx_ether[] = CLAT_INGRESS6_PROG_ETHER_NAME FSOBJ_SUFFIX;
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_egress4_clat_rawip:[*fsobj]
+ // and is the name of the pinned egress ebpf program for ARPHRD_RAWIP interfaces.
+ // (also compatible with anything that has 0 size L2 header)
+ static constexpr char name_clat_tx_rawip[] = CLAT_EGRESS4_PROG_RAWIP_NAME FSOBJ_SUFFIX;
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_egress4_clat_ether:[*fsobj]
+ // and is the name of the pinned egress ebpf program for ARPHRD_ETHER interfaces.
+ // (also compatible with anything that has standard ethernet header)
+ static constexpr char name_clat_tx_ether[] = CLAT_EGRESS4_PROG_ETHER_NAME FSOBJ_SUFFIX;
+
+#undef FSOBJ_SUFFIX
+
+ // The actual name we'll use is determined at run time via 'ethernet' and 'ingress'
+ // booleans. We need to compile time allocate enough space in the struct
+ // hence this macro magic to make sure we have enough space for either
+ // possibility. In practice some of these are actually the same size.
+ static constexpr size_t ASCIIZ_MAXLEN_NAME = max({
+ sizeof(name_clat_rx_rawip),
+ sizeof(name_clat_rx_ether),
+ sizeof(name_clat_tx_rawip),
+ sizeof(name_clat_tx_ether),
+ });
+
+ // These are not compile time constants: 'name' is used in strncpy below
+ const char* const name_clat_rx = ethernet ? name_clat_rx_ether : name_clat_rx_rawip;
+ const char* const name_clat_tx = ethernet ? name_clat_tx_ether : name_clat_tx_rawip;
+ const char* const name = ingress ? name_clat_rx : name_clat_tx;
+
+ struct {
+ nlmsghdr n;
+ tcmsg t;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(ASCIIZ_LEN_BPF)];
+ } kind;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } fd;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(ASCIIZ_MAXLEN_NAME)];
+ } name;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } flags;
+ } options;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_NEWTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = static_cast<__u32>((PRIO_CLAT << 16) | htons(proto)),
+ },
+ .kind =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.kind),
+ .nla_type = TCA_KIND,
+ },
+ .str = BPF,
+ },
+ .options =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options),
+ .nla_type = NLA_F_NESTED | TCA_OPTIONS,
+ },
+ .fd =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.fd),
+ .nla_type = TCA_BPF_FD,
+ },
+ .u32 = static_cast<__u32>(bpfFd),
+ },
+ .name =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.name),
+ .nla_type = TCA_BPF_NAME,
+ },
+ // Visible via 'tc filter show', but
+ // is overwritten by strncpy below
+ .str = "placeholder",
+ },
+ .flags =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.flags),
+ .nla_type = TCA_BPF_FLAGS,
+ },
+ .u32 = TCA_BPF_FLAG_ACT_DIRECT,
+ },
+ },
+ };
+#undef BPF
+
+ strncpy(req.options.name.str, name, sizeof(req.options.name.str));
+
+ return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+// tc filter del dev .. in/egress prio 4 protocol ..
+int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) {
+ const struct {
+ nlmsghdr n;
+ tcmsg t;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_DELTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = (static_cast<uint32_t>(prio) << 16) |
+ static_cast<uint32_t>(htons(proto)),
+ },
+ };
+
+ return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/TcUtilsTest.cpp b/service/native/libs/libclat/TcUtilsTest.cpp
new file mode 100644
index 0000000..08f3042
--- /dev/null
+++ b/service/native/libs/libclat/TcUtilsTest.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * TcUtilsTest.cpp - unit tests for TcUtils.cpp
+ */
+
+#include <gtest/gtest.h>
+
+#include "libclat/TcUtils.h"
+
+#include <linux/if_arp.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "bpf/BpfUtils.h"
+#include "bpf_shared.h"
+
+namespace android {
+namespace net {
+
+class TcUtilsTest : public ::testing::Test {
+ public:
+ void SetUp() {}
+};
+
+TEST_F(TcUtilsTest, HardwareAddressTypeOfNonExistingIf) {
+ ASSERT_EQ(-ENODEV, hardwareAddressType("not_existing_if"));
+}
+
+TEST_F(TcUtilsTest, HardwareAddressTypeOfLoopback) {
+ ASSERT_EQ(ARPHRD_LOOPBACK, hardwareAddressType("lo"));
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+TEST_F(TcUtilsTest, HardwareAddressTypeOfWireless) {
+ int type = hardwareAddressType("wlan0");
+ if (type == -ENODEV) return;
+
+ ASSERT_EQ(ARPHRD_ETHER, type);
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+TEST_F(TcUtilsTest, HardwareAddressTypeOfCellular) {
+ int type = hardwareAddressType("rmnet_data0");
+ if (type == -ENODEV) return;
+
+ ASSERT_NE(ARPHRD_ETHER, type);
+
+ // ARPHRD_RAWIP is 530 on some pre-4.14 Qualcomm devices.
+ if (type == 530) return;
+
+ ASSERT_EQ(ARPHRD_RAWIP, type);
+}
+
+TEST_F(TcUtilsTest, IsEthernetOfNonExistingIf) {
+ auto res = isEthernet("not_existing_if");
+ ASSERT_FALSE(res.ok());
+ ASSERT_EQ(ENODEV, res.error().code());
+}
+
+TEST_F(TcUtilsTest, IsEthernetOfLoopback) {
+ auto res = isEthernet("lo");
+ ASSERT_FALSE(res.ok());
+ ASSERT_EQ(EAFNOSUPPORT, res.error().code());
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+// See also HardwareAddressTypeOfWireless.
+TEST_F(TcUtilsTest, IsEthernetOfWireless) {
+ auto res = isEthernet("wlan0");
+ if (!res.ok() && res.error().code() == ENODEV) return;
+
+ ASSERT_RESULT_OK(res);
+ ASSERT_TRUE(res.value());
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+// See also HardwareAddressTypeOfCellular.
+TEST_F(TcUtilsTest, IsEthernetOfCellular) {
+ auto res = isEthernet("rmnet_data0");
+ if (!res.ok() && res.error().code() == ENODEV) return;
+
+ ASSERT_RESULT_OK(res);
+ ASSERT_FALSE(res.value());
+}
+
+TEST_F(TcUtilsTest, DeviceMTUOfNonExistingIf) {
+ ASSERT_EQ(-ENODEV, deviceMTU("not_existing_if"));
+}
+
+TEST_F(TcUtilsTest, DeviceMTUofLoopback) {
+ ASSERT_EQ(65536, deviceMTU("lo"));
+}
+
+TEST_F(TcUtilsTest, GetClatEgress4MapFd) {
+ int fd = getClatEgress4MapFd();
+ ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatEgress4RawIpProgFd) {
+ int fd = getClatEgress4ProgFd(RAWIP);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatEgress4EtherProgFd) {
+ int fd = getClatEgress4ProgFd(ETHER);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatIngress6MapFd) {
+ int fd = getClatIngress6MapFd();
+ ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatIngress6RawIpProgFd) {
+ int fd = getClatIngress6ProgFd(RAWIP);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatIngress6EtherProgFd) {
+ int fd = getClatIngress6ProgFd(ETHER);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+// See Linux kernel source in include/net/flow.h
+#define LOOPBACK_IFINDEX 1
+
+TEST_F(TcUtilsTest, AttachReplaceDetachClsactLo) {
+ // This attaches and detaches a configuration-less and thus no-op clsact
+ // qdisc to loopback interface (and it takes fractions of a second)
+ EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscReplaceDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
+}
+
+static void checkAttachDetachBpfFilterClsactLo(const bool ingress, const bool ethernet) {
+ // Older kernels return EINVAL instead of ENOENT due to lacking proper error propagation...
+ const int errNOENT = android::bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+
+ int clatBpfFd = ingress ? getClatIngress6ProgFd(ethernet) : getClatEgress4ProgFd(ethernet);
+ ASSERT_GE(clatBpfFd, 3);
+
+ // This attaches and detaches a clsact plus ebpf program to loopback
+ // interface, but it should not affect traffic by virtue of us not
+ // actually populating the ebpf control map.
+ // Furthermore: it only takes fractions of a second.
+ EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ if (ingress) {
+ EXPECT_EQ(0, tcFilterAddDevIngressClatIpv6(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
+ EXPECT_EQ(0, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ } else {
+ EXPECT_EQ(0, tcFilterAddDevEgressClatIpv4(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
+ EXPECT_EQ(0, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ }
+ EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+
+ close(clatBpfFd);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactEgressLo) {
+ checkAttachDetachBpfFilterClsactLo(EGRESS, RAWIP);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactEgressLo) {
+ checkAttachDetachBpfFilterClsactLo(EGRESS, ETHER);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactIngressLo) {
+ checkAttachDetachBpfFilterClsactLo(INGRESS, RAWIP);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactIngressLo) {
+ checkAttachDetachBpfFilterClsactLo(INGRESS, ETHER);
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/bpfhelper.cpp b/service/native/libs/libclat/bpfhelper.cpp
new file mode 100644
index 0000000..6e230d0
--- /dev/null
+++ b/service/native/libs/libclat/bpfhelper.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * main.c - main function
+ */
+#define LOG_TAG "bpfhelper"
+
+#include "libclat/bpfhelper.h"
+
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+
+#include "bpf/BpfMap.h"
+#include "libclat/TcUtils.h"
+
+#define DEVICEPREFIX "v4-"
+
+using android::base::unique_fd;
+using android::net::RAWIP;
+using android::net::getClatEgress4MapFd;
+using android::net::getClatIngress6MapFd;
+using android::net::getClatEgress4ProgFd;
+using android::net::getClatIngress6ProgFd;
+using android::net::tcQdiscAddDevClsact;
+using android::net::tcFilterAddDevEgressClatIpv4;
+using android::net::tcFilterAddDevIngressClatIpv6;
+using android::net::tcFilterDelDevEgressClatIpv4;
+using android::net::tcFilterDelDevIngressClatIpv6;
+using android::bpf::BpfMap;
+
+BpfMap<ClatEgress4Key, ClatEgress4Value> mClatEgress4Map;
+BpfMap<ClatIngress6Key, ClatIngress6Value> mClatIngress6Map;
+
+namespace android {
+namespace net {
+namespace clat {
+
+// TODO: have a clearMap function to remove all stubs while system server crash.
+// For long term, move bpf access into java and map initialization should live
+// ClatCoordinator constructor.
+int initMaps(void) {
+ int rv = getClatEgress4MapFd();
+ if (rv < 0) {
+ ALOGE("getClatEgress4MapFd() failure: %s", strerror(-rv));
+ return -rv;
+ }
+ mClatEgress4Map.reset(rv);
+
+ rv = getClatIngress6MapFd();
+ if (rv < 0) {
+ ALOGE("getClatIngress6MapFd() failure: %s", strerror(-rv));
+ mClatEgress4Map.reset(-1);
+ return -rv;
+ }
+ mClatIngress6Map.reset(rv);
+
+ return 0;
+}
+
+void maybeStartBpf(const ClatdTracker& tracker) {
+ auto isEthernet = android::net::isEthernet(tracker.iface);
+ if (!isEthernet.ok()) {
+ ALOGE("isEthernet(%s[%d]) failure: %s", tracker.iface, tracker.ifIndex,
+ isEthernet.error().message().c_str());
+ return;
+ }
+
+ // This program will be attached to the v4-* interface which is a TUN and thus always rawip.
+ int rv = getClatEgress4ProgFd(RAWIP);
+ if (rv < 0) {
+ ALOGE("getClatEgress4ProgFd(RAWIP) failure: %s", strerror(-rv));
+ return;
+ }
+ unique_fd txRawIpProgFd(rv);
+
+ rv = getClatIngress6ProgFd(isEthernet.value());
+ if (rv < 0) {
+ ALOGE("getClatIngress6ProgFd(%d) failure: %s", isEthernet.value(), strerror(-rv));
+ return;
+ }
+ unique_fd rxProgFd(rv);
+
+ ClatEgress4Key txKey = {
+ .iif = tracker.v4ifIndex,
+ .local4 = tracker.v4,
+ };
+ ClatEgress4Value txValue = {
+ .oif = tracker.ifIndex,
+ .local6 = tracker.v6,
+ .pfx96 = tracker.pfx96,
+ .oifIsEthernet = isEthernet.value(),
+ };
+
+ auto ret = mClatEgress4Map.writeValue(txKey, txValue, BPF_ANY);
+ if (!ret.ok()) {
+ ALOGE("mClatEgress4Map.writeValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ ClatIngress6Key rxKey = {
+ .iif = tracker.ifIndex,
+ .pfx96 = tracker.pfx96,
+ .local6 = tracker.v6,
+ };
+ ClatIngress6Value rxValue = {
+ // TODO: move all the clat code to eBPF and remove the tun interface entirely.
+ .oif = tracker.v4ifIndex,
+ .local4 = tracker.v4,
+ };
+
+ ret = mClatIngress6Map.writeValue(rxKey, rxValue, BPF_ANY);
+ if (!ret.ok()) {
+ ALOGE("mClatIngress6Map.writeValue failure: %s", strerror(ret.error().code()));
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ // We do tc setup *after* populating the maps, so scanning through them
+ // can always be used to tell us what needs cleanup.
+
+ // Usually the clsact will be added in RouteController::addInterfaceToPhysicalNetwork.
+ // But clat is started before the v4- interface is added to the network. The clat startup have
+ // to add clsact of v4- tun interface first for adding bpf filter in maybeStartBpf.
+ // TODO: move "qdisc add clsact" of v4- tun interface out from ClatdController.
+ rv = tcQdiscAddDevClsact(tracker.v4ifIndex);
+ if (rv) {
+ ALOGE("tcQdiscAddDevClsact(%d[%s]) failure: %s", tracker.v4ifIndex, tracker.v4iface,
+ strerror(-rv));
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok())
+ ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ rv = tcFilterAddDevEgressClatIpv4(tracker.v4ifIndex, txRawIpProgFd, RAWIP);
+ if (rv) {
+ ALOGE("tcFilterAddDevEgressClatIpv4(%d[%s], RAWIP) failure: %s", tracker.v4ifIndex,
+ tracker.v4iface, strerror(-rv));
+
+ // The v4- interface clsact is not deleted for unwinding error because once it is created
+ // with interface addition, the lifetime is till interface deletion. Moreover, the clsact
+ // has no clat filter now. It should not break anything.
+
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok())
+ ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ rv = tcFilterAddDevIngressClatIpv6(tracker.ifIndex, rxProgFd, isEthernet.value());
+ if (rv) {
+ ALOGE("tcFilterAddDevIngressClatIpv6(%d[%s], %d) failure: %s", tracker.ifIndex,
+ tracker.iface, isEthernet.value(), strerror(-rv));
+ rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
+ if (rv) {
+ ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
+ tracker.v4iface, strerror(-rv));
+ }
+
+ // The v4- interface clsact is not deleted. See the reason in the error unwinding code of
+ // the egress filter attaching of v4- tun interface.
+
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok())
+ ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ // success
+}
+
+void maybeStopBpf(const ClatdTracker& tracker) {
+ int rv = tcFilterDelDevIngressClatIpv6(tracker.ifIndex);
+ if (rv < 0) {
+ ALOGE("tcFilterDelDevIngressClatIpv6(%d[%s]) failure: %s", tracker.ifIndex, tracker.iface,
+ strerror(-rv));
+ }
+
+ rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
+ if (rv < 0) {
+ ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
+ tracker.v4iface, strerror(-rv));
+ }
+
+ // We cleanup the maps last, so scanning through them can be used to
+ // determine what still needs cleanup.
+
+ ClatEgress4Key txKey = {
+ .iif = tracker.v4ifIndex,
+ .local4 = tracker.v4,
+ };
+
+ auto ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok()) ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+
+ ClatIngress6Key rxKey = {
+ .iif = tracker.ifIndex,
+ .pfx96 = tracker.pfx96,
+ .local6 = tracker.v6,
+ };
+
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok()) ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+}
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp
new file mode 100644
index 0000000..4a125ba
--- /dev/null
+++ b/service/native/libs/libclat/clatutils.cpp
@@ -0,0 +1,268 @@
+// 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.
+
+#define LOG_TAG "clatutils"
+
+#include "libclat/clatutils.h"
+
+#include <errno.h>
+#include <linux/filter.h>
+#include <linux/if_packet.h>
+#include <linux/if_tun.h>
+#include <log/log.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+#include "checksum.h"
+}
+
+// Sync from external/android-clat/clatd.h
+#define MAXMTU 65536
+#define PACKETLEN (MAXMTU + sizeof(struct tun_pi))
+
+// Sync from system/netd/include/netid_client.h.
+#define MARK_UNSET 0u
+
+namespace android {
+namespace net {
+namespace clat {
+
+bool isIpv4AddressFree(in_addr_t addr) {
+ int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (s == -1) {
+ return 0;
+ }
+
+ // Attempt to connect to the address. If the connection succeeds and getsockname returns the
+ // same then the address is already assigned to the system and we can't use it.
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_port = htons(53),
+ .sin_addr = {addr},
+ };
+ socklen_t len = sizeof(sin);
+ bool inuse = connect(s, (struct sockaddr*)&sin, sizeof(sin)) == 0 &&
+ getsockname(s, (struct sockaddr*)&sin, &len) == 0 && (size_t)len >= sizeof(sin) &&
+ sin.sin_addr.s_addr == addr;
+
+ close(s);
+ return !inuse;
+}
+
+// Picks a free IPv4 address, starting from ip and trying all addresses in the prefix in order.
+// ip - the IP address from the configuration file
+// prefixlen - the length of the prefix from which addresses may be selected.
+// returns: the IPv4 address, or INADDR_NONE if no addresses were available
+in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen) {
+ return selectIpv4AddressInternal(ip, prefixlen, isIpv4AddressFree);
+}
+
+// Only allow testing to use this function directly. Otherwise call selectIpv4Address(ip, pfxlen)
+// which has applied valid isIpv4AddressFree function pointer.
+in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen,
+ isIpv4AddrFreeFn isIpv4AddressFreeFunc) {
+ // Impossible! Only test allows to apply fn.
+ if (isIpv4AddressFreeFunc == nullptr) {
+ return INADDR_NONE;
+ }
+
+ // Don't accept prefixes that are too large because we scan addresses one by one.
+ if (prefixlen < 16 || prefixlen > 32) {
+ return INADDR_NONE;
+ }
+
+ // All these are in host byte order.
+ in_addr_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen);
+ in_addr_t ipv4 = ntohl(ip.s_addr);
+ in_addr_t first_ipv4 = ipv4;
+ in_addr_t prefix = ipv4 & mask;
+
+ // Pick the first IPv4 address in the pool, wrapping around if necessary.
+ // So, for example, 192.0.0.4 -> 192.0.0.5 -> 192.0.0.6 -> 192.0.0.7 -> 192.0.0.0.
+ do {
+ if (isIpv4AddressFreeFunc(htonl(ipv4))) {
+ return htonl(ipv4);
+ }
+ ipv4 = prefix | ((ipv4 + 1) & ~mask);
+ } while (ipv4 != first_ipv4);
+
+ return INADDR_NONE;
+}
+
+// Alters the bits in the IPv6 address to make them checksum neutral with v4 and nat64Prefix.
+void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix) {
+ // Fill last 8 bytes of IPv6 address with random bits.
+ arc4random_buf(&v6->s6_addr[8], 8);
+
+ // Make the IID checksum-neutral. That is, make it so that:
+ // checksum(Local IPv4 | Remote IPv4) = checksum(Local IPv6 | Remote IPv6)
+ // in other words (because remote IPv6 = NAT64 prefix | Remote IPv4):
+ // checksum(Local IPv4) = checksum(Local IPv6 | NAT64 prefix)
+ // Do this by adjusting the two bytes in the middle of the IID.
+
+ uint16_t middlebytes = (v6->s6_addr[11] << 8) + v6->s6_addr[12];
+
+ uint32_t c1 = ip_checksum_add(0, &v4, sizeof(v4));
+ uint32_t c2 = ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
+ ip_checksum_add(0, v6, sizeof(*v6));
+
+ uint16_t delta = ip_checksum_adjust(middlebytes, c1, c2);
+ v6->s6_addr[11] = delta >> 8;
+ v6->s6_addr[12] = delta & 0xff;
+}
+
+// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
+int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
+ in6_addr* v6) {
+ int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (s == -1) return -errno;
+
+ if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) == -1) {
+ close(s);
+ return -errno;
+ }
+
+ sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = nat64Prefix};
+ if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)) == -1) {
+ close(s);
+ return -errno;
+ }
+
+ socklen_t len = sizeof(sin6);
+ if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len) == -1) {
+ close(s);
+ return -errno;
+ }
+
+ *v6 = sin6.sin6_addr;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(v6) || IN6_IS_ADDR_LOOPBACK(v6) || IN6_IS_ADDR_LINKLOCAL(v6) ||
+ IN6_IS_ADDR_SITELOCAL(v6) || IN6_IS_ADDR_ULA(v6)) {
+ close(s);
+ return -ENETUNREACH;
+ }
+
+ makeChecksumNeutral(v6, v4, nat64Prefix);
+ close(s);
+
+ return 0;
+}
+
+int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark) {
+ // Create an IPv6 UDP socket.
+ int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (s < 0) {
+ int ret = errno;
+ ALOGE("socket(AF_INET6, SOCK_DGRAM, 0) failed: %s", strerror(errno));
+ return -ret;
+ }
+
+ // Socket's mark affects routing decisions (network selection)
+ if ((mark != MARK_UNSET) && setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
+ int ret = errno;
+ ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(errno));
+ close(s);
+ return -ret;
+ }
+
+ // Try to connect udp socket to plat_subnet(96 bits):plat_suffix(32 bits)
+ struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = *plat_subnet,
+ };
+ dst.sin6_addr.s6_addr32[3] = plat_suffix;
+ if (connect(s, (struct sockaddr*)&dst, sizeof(dst))) {
+ int ret = errno;
+ ALOGE("connect() failed: %s", strerror(errno));
+ close(s);
+ return -ret;
+ }
+
+ // Fetch the socket's IPv6 mtu - this is effectively fetching mtu from routing table
+ int mtu;
+ socklen_t sz_mtu = sizeof(mtu);
+ if (getsockopt(s, SOL_IPV6, IPV6_MTU, &mtu, &sz_mtu)) {
+ int ret = errno;
+ ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) failed: %s", strerror(errno));
+ close(s);
+ return -ret;
+ }
+ if (sz_mtu != sizeof(mtu)) {
+ ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) returned unexpected size: %d", sz_mtu);
+ close(s);
+ return -EFAULT;
+ }
+ close(s);
+
+ return mtu;
+}
+
+/* function: configure_packet_socket
+ * Binds the packet socket and attaches the receive filter to it.
+ * sock - the socket to configure
+ * addr - the IP address to filter
+ * ifindex - index of interface to add the filter to
+ * returns: 0 on success, -errno on failure
+ */
+int configure_packet_socket(int sock, in6_addr* addr, int ifindex) {
+ uint32_t* ipv6 = addr->s6_addr32;
+
+ // clang-format off
+ struct sock_filter filter_code[] = {
+ // Load the first four bytes of the IPv6 destination address (starts 24 bytes in).
+ // Compare it against the first four bytes of our IPv6 address, in host byte order (BPF loads
+ // are always in host byte order). If it matches, continue with next instruction (JMP 0). If it
+ // doesn't match, jump ahead to statement that returns 0 (ignore packet). Repeat for the other
+ // three words of the IPv6 address, and if they all match, return PACKETLEN (accept packet).
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 24),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[0]), 0, 7),
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 28),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[1]), 0, 5),
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 32),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[2]), 0, 3),
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 36),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[3]), 0, 1),
+ BPF_STMT(BPF_RET | BPF_K, PACKETLEN),
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ };
+ // clang-format on
+ struct sock_fprog filter = {sizeof(filter_code) / sizeof(filter_code[0]), filter_code};
+
+ if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) {
+ int res = errno;
+ ALOGE("attach packet filter failed: %s", strerror(errno));
+ return -res;
+ }
+
+ struct sockaddr_ll sll = {
+ .sll_family = AF_PACKET,
+ .sll_protocol = htons(ETH_P_IPV6),
+ .sll_ifindex = ifindex,
+ .sll_pkttype =
+ PACKET_OTHERHOST, // The 464xlat IPv6 address is not assigned to the kernel.
+ };
+ if (bind(sock, (struct sockaddr*)&sll, sizeof(sll))) {
+ int res = errno;
+ ALOGE("binding packet socket: %s", strerror(errno));
+ return -res;
+ }
+
+ return 0;
+}
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp
new file mode 100644
index 0000000..4153e19
--- /dev/null
+++ b/service/native/libs/libclat/clatutils_test.cpp
@@ -0,0 +1,187 @@
+// 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.
+
+#include "libclat/clatutils.h"
+
+#include <android-base/stringprintf.h>
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+#include <linux/if_packet.h>
+#include <linux/if_tun.h>
+#include "tun_interface.h"
+
+extern "C" {
+#include "checksum.h"
+}
+
+// Default translation parameters.
+static const char kIPv4LocalAddr[] = "192.0.0.4";
+
+namespace android {
+namespace net {
+namespace clat {
+
+using android::net::TunInterface;
+using base::StringPrintf;
+
+class ClatUtils : public ::testing::Test {};
+
+// Mock functions for isIpv4AddressFree.
+bool neverFree(in_addr_t /* addr */) {
+ return 0;
+}
+bool alwaysFree(in_addr_t /* addr */) {
+ return 1;
+}
+bool only2Free(in_addr_t addr) {
+ return (ntohl(addr) & 0xff) == 2;
+}
+bool over6Free(in_addr_t addr) {
+ return (ntohl(addr) & 0xff) >= 6;
+}
+bool only10Free(in_addr_t addr) {
+ return (ntohl(addr) & 0xff) == 10;
+}
+
+// Apply mocked isIpv4AddressFree function for selectIpv4Address test.
+in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen,
+ isIpv4AddrFreeFn fn /* mocked function */) {
+ // Call internal function to replace isIpv4AddressFreeFn for testing.
+ return selectIpv4AddressInternal(ip, prefixlen, fn);
+}
+
+TEST_F(ClatUtils, SelectIpv4Address) {
+ struct in_addr addr;
+
+ inet_pton(AF_INET, kIPv4LocalAddr, &addr);
+
+ // If no addresses are free, return INADDR_NONE.
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, neverFree));
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 16, neverFree));
+
+ // If the configured address is free, pick that. But a prefix that's too big is invalid.
+ EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 29, alwaysFree));
+ EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 20, alwaysFree));
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 15, alwaysFree));
+
+ // A prefix length of 32 works, but anything above it is invalid.
+ EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 32, alwaysFree));
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 33, alwaysFree));
+
+ // If another address is free, pick it.
+ EXPECT_EQ(inet_addr("192.0.0.6"), selectIpv4Address(addr, 29, over6Free));
+
+ // Check that we wrap around to addresses that are lower than the first address.
+ EXPECT_EQ(inet_addr("192.0.0.2"), selectIpv4Address(addr, 29, only2Free));
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 30, only2Free));
+
+ // If a free address exists outside the prefix, we don't pick it.
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, only10Free));
+ EXPECT_EQ(inet_addr("192.0.0.10"), selectIpv4Address(addr, 24, only10Free));
+
+ // Now try using the real function which sees if IP addresses are free using bind().
+ // Assume that the machine running the test has the address 127.0.0.1, but not 8.8.8.8.
+ addr.s_addr = inet_addr("8.8.8.8");
+ EXPECT_EQ(inet_addr("8.8.8.8"), selectIpv4Address(addr, 29));
+
+ addr.s_addr = inet_addr("127.0.0.1");
+ EXPECT_EQ(inet_addr("127.0.0.2"), selectIpv4Address(addr, 29));
+}
+
+TEST_F(ClatUtils, MakeChecksumNeutral) {
+ // We can't test generateIPv6Address here since it requires manipulating routing, which we can't
+ // do without talking to the real netd on the system.
+ uint32_t rand = arc4random_uniform(0xffffffff);
+ uint16_t rand1 = rand & 0xffff;
+ uint16_t rand2 = (rand >> 16) & 0xffff;
+ std::string v6PrefixStr = StringPrintf("2001:db8:%x:%x", rand1, rand2);
+ std::string v6InterfaceAddrStr = StringPrintf("%s::%x:%x", v6PrefixStr.c_str(), rand2, rand1);
+ std::string nat64PrefixStr = StringPrintf("2001:db8:%x:%x::", rand2, rand1);
+
+ in_addr v4 = {inet_addr(kIPv4LocalAddr)};
+ in6_addr v6InterfaceAddr;
+ ASSERT_TRUE(inet_pton(AF_INET6, v6InterfaceAddrStr.c_str(), &v6InterfaceAddr));
+ in6_addr nat64Prefix;
+ ASSERT_TRUE(inet_pton(AF_INET6, nat64PrefixStr.c_str(), &nat64Prefix));
+
+ // Generate a boatload of random IIDs.
+ int onebits = 0;
+ uint64_t prev_iid = 0;
+ for (int i = 0; i < 100000; i++) {
+ in6_addr v6 = v6InterfaceAddr;
+ makeChecksumNeutral(&v6, v4, nat64Prefix);
+
+ // Check the generated IP address is in the same prefix as the interface IPv6 address.
+ EXPECT_EQ(0, memcmp(&v6, &v6InterfaceAddr, 8));
+
+ // Check that consecutive IIDs are not the same.
+ uint64_t iid = *(uint64_t*)(&v6.s6_addr[8]);
+ ASSERT_TRUE(iid != prev_iid)
+ << "Two consecutive random IIDs are the same: " << std::showbase << std::hex << iid
+ << "\n";
+ prev_iid = iid;
+
+ // Check that the IID is checksum-neutral with the NAT64 prefix and the
+ // local prefix.
+ uint16_t c1 = ip_checksum_finish(ip_checksum_add(0, &v4, sizeof(v4)));
+ uint16_t c2 = ip_checksum_finish(ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
+ ip_checksum_add(0, &v6, sizeof(v6)));
+
+ if (c1 != c2) {
+ char v6Str[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &v6, v6Str, sizeof(v6Str));
+ FAIL() << "Bad IID: " << v6Str << " not checksum-neutral with " << kIPv4LocalAddr
+ << " and " << nat64PrefixStr.c_str() << std::showbase << std::hex
+ << "\n IPv4 checksum: " << c1 << "\n IPv6 checksum: " << c2 << "\n";
+ }
+
+ // Check that IIDs are roughly random and use all the bits by counting the
+ // total number of bits set to 1 in a random sample of 100000 generated IIDs.
+ onebits += __builtin_popcountll(*(uint64_t*)&iid);
+ }
+ EXPECT_LE(3190000, onebits);
+ EXPECT_GE(3210000, onebits);
+}
+
+TEST_F(ClatUtils, DetectMtu) {
+ // ::1 with bottom 32 bits set to 1 is still ::1 which routes via lo with mtu of 64KiB
+ ASSERT_EQ(detect_mtu(&in6addr_loopback, htonl(1), 0 /*MARK_UNSET*/), 65536);
+}
+
+TEST_F(ClatUtils, ConfigurePacketSocket) {
+ // Create an interface for configure_packet_socket to attach socket filter to.
+ TunInterface v6Iface;
+ ASSERT_EQ(0, v6Iface.init());
+
+ int s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
+ EXPECT_LE(0, s);
+ struct in6_addr addr6;
+ EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::f00", &addr6));
+ EXPECT_EQ(0, configure_packet_socket(s, &addr6, v6Iface.ifindex()));
+
+ // Check that the packet socket is bound to the interface. We can't check the socket filter
+ // because there is no way to fetch it from the kernel.
+ sockaddr_ll sll;
+ socklen_t len = sizeof(sll);
+ ASSERT_EQ(0, getsockname(s, reinterpret_cast<sockaddr*>(&sll), &len));
+ EXPECT_EQ(htons(ETH_P_IPV6), sll.sll_protocol);
+ EXPECT_EQ(sll.sll_ifindex, v6Iface.ifindex());
+
+ close(s);
+ v6Iface.destroy();
+}
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/TcUtils.h b/service/native/libs/libclat/include/libclat/TcUtils.h
new file mode 100644
index 0000000..212838e
--- /dev/null
+++ b/service/native/libs/libclat/include/libclat/TcUtils.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <errno.h>
+#include <linux/if_ether.h>
+#include <linux/if_link.h>
+#include <linux/rtnetlink.h>
+
+#include <string>
+
+#include "bpf/BpfUtils.h"
+#include "bpf_shared.h"
+
+namespace android {
+namespace net {
+
+// For better code clarity - do not change values - used for booleans like
+// with_ethernet_header or isEthernet.
+constexpr bool RAWIP = false;
+constexpr bool ETHER = true;
+
+// For better code clarity when used for 'bool ingress' parameter.
+constexpr bool EGRESS = false;
+constexpr bool INGRESS = true;
+
+// The priority of clat hook - must be after tethering.
+constexpr uint16_t PRIO_CLAT = 4;
+
+// this returns an ARPHRD_* constant or a -errno
+int hardwareAddressType(const std::string& interface);
+
+// return MTU or -errno
+int deviceMTU(const std::string& interface);
+
+base::Result<bool> isEthernet(const std::string& interface);
+
+inline int getClatEgress4MapFd(void) {
+ const int fd = bpf::mapRetrieveRW(CLAT_EGRESS4_MAP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+inline int getClatEgress4ProgFd(bool with_ethernet_header) {
+ const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_EGRESS4_PROG_ETHER_PATH
+ : CLAT_EGRESS4_PROG_RAWIP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+inline int getClatIngress6MapFd(void) {
+ const int fd = bpf::mapRetrieveRW(CLAT_INGRESS6_MAP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+inline int getClatIngress6ProgFd(bool with_ethernet_header) {
+ const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_INGRESS6_PROG_ETHER_PATH
+ : CLAT_INGRESS6_PROG_RAWIP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags);
+
+inline int tcQdiscAddDevClsact(int ifIndex) {
+ return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE);
+}
+
+inline int tcQdiscReplaceDevClsact(int ifIndex) {
+ return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE);
+}
+
+inline int tcQdiscDelDevClsact(int ifIndex) {
+ return doTcQdiscClsact(ifIndex, RTM_DELQDISC, 0);
+}
+
+// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// direct-action
+int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet);
+
+// tc filter add dev .. ingress prio 4 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
+inline int tcFilterAddDevIngressClatIpv6(int ifIndex, int bpfFd, bool ethernet) {
+ return tcFilterAddDevBpf(ifIndex, INGRESS, ETH_P_IPV6, bpfFd, ethernet);
+}
+
+// tc filter add dev .. egress prio 4 protocol ip bpf object-pinned /sys/fs/bpf/... direct-action
+inline int tcFilterAddDevEgressClatIpv4(int ifIndex, int bpfFd, bool ethernet) {
+ return tcFilterAddDevBpf(ifIndex, EGRESS, ETH_P_IP, bpfFd, ethernet);
+}
+
+// tc filter del dev .. in/egress prio .. protocol ..
+int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
+
+// tc filter del dev .. ingress prio 4 protocol ipv6
+inline int tcFilterDelDevIngressClatIpv6(int ifIndex) {
+ return tcFilterDelDev(ifIndex, INGRESS, PRIO_CLAT, ETH_P_IPV6);
+}
+
+// tc filter del dev .. egress prio 4 protocol ip
+inline int tcFilterDelDevEgressClatIpv4(int ifIndex) {
+ return tcFilterDelDev(ifIndex, EGRESS, PRIO_CLAT, ETH_P_IP);
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/bpfhelper.h b/service/native/libs/libclat/include/libclat/bpfhelper.h
new file mode 100644
index 0000000..c0328c0
--- /dev/null
+++ b/service/native/libs/libclat/include/libclat/bpfhelper.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+
+namespace android {
+namespace net {
+namespace clat {
+
+struct ClatdTracker {
+ unsigned ifIndex;
+ char iface[IFNAMSIZ];
+ unsigned v4ifIndex;
+ char v4iface[IFNAMSIZ];
+ in_addr v4;
+ in6_addr v6;
+ in6_addr pfx96;
+};
+
+int initMaps(void);
+void maybeStartBpf(const ClatdTracker& tracker);
+void maybeStopBpf(const ClatdTracker& tracker);
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h
new file mode 100644
index 0000000..812c86e
--- /dev/null
+++ b/service/native/libs/libclat/include/libclat/clatutils.h
@@ -0,0 +1,37 @@
+// 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.
+
+#pragma once
+#include <netinet/in.h>
+#include <netinet/in6.h>
+
+namespace android {
+namespace net {
+namespace clat {
+
+bool isIpv4AddressFree(in_addr_t addr);
+in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen);
+void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix);
+int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
+ in6_addr* v6);
+int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark);
+int configure_packet_socket(int sock, in6_addr* addr, int ifindex);
+
+// For testing
+typedef bool (*isIpv4AddrFreeFn)(in_addr_t);
+in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen, isIpv4AddrFreeFn fn);
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
new file mode 100644
index 0000000..9d89788
--- /dev/null
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -0,0 +1,261 @@
+/*
+ * 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 com.android.server;
+
+import android.os.ServiceSpecificException;
+import android.system.Os;
+import android.util.Log;
+
+/**
+ * BpfNetMaps is responsible for providing traffic controller relevant functionality.
+ *
+ * {@hide}
+ */
+public class BpfNetMaps {
+ private static final String TAG = "BpfNetMaps";
+
+ static {
+ System.loadLibrary("traffic_controller_jni");
+ native_init();
+ }
+
+ /**
+ * 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) {
+ final int err = native_addNaughtyApp(uid);
+ if (err != 0) {
+ throw new ServiceSpecificException(err, "Unable to add naughty app: "
+ + Os.strerror(err));
+ }
+ }
+
+ /**
+ * 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) {
+ final int err = native_removeNaughtyApp(uid);
+ if (err != 0) {
+ throw new ServiceSpecificException(err, "Unable to remove naughty app: "
+ + Os.strerror(err));
+ }
+ }
+
+ /**
+ * 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) {
+ final int err = native_addNiceApp(uid);
+ if (err != 0) {
+ throw new ServiceSpecificException(err, "Unable to add nice app: "
+ + Os.strerror(err));
+ }
+ }
+
+ /**
+ * 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) {
+ final int err = native_removeNiceApp(uid);
+ if (err != 0) {
+ throw new ServiceSpecificException(err, "Unable to remove nice app: "
+ + Os.strerror(err));
+ }
+ }
+
+ /**
+ * 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) {
+ final int err = native_setChildChain(childChain, enable);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to set child chain: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Replaces the contents of the specified UID-based firewall chain.
+ *
+ * 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
+ * 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 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.
+ */
+ public int replaceUidChain(final String chainName, final boolean isAllowlist,
+ final int[] uids) {
+ final int err = native_replaceUidChain(chainName, isAllowlist, uids);
+ if (err != 0) {
+ Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
+ }
+ 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) {
+ final int err = native_setUidRule(childChain, uid, firewallRule);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to set uid rule: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Add ingress interface filtering rules to a list of UIDs
+ *
+ * For a given uid, once a filtering rule is added, the kernel will only allow packets from the
+ * allowed interface and loopback to be sent to the list of UIDs.
+ *
+ * Calling this method on one or more UIDs with an existing filtering rule but a different
+ * interface name will result in the filtering rule being updated to allow the new interface
+ * 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
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addUidInterfaceRules(final String ifName, final int[] uids) {
+ final int err = native_addUidInterfaceRules(ifName, uids);
+ if (err != 0) {
+ throw new ServiceSpecificException(err, "Unable to add uid interface rules: "
+ + Os.strerror(err));
+ }
+ }
+
+ /**
+ * Remove ingress interface filtering rules from a list of UIDs
+ *
+ * Clear the ingress interface filtering rules from the list of UIDs which were previously set
+ * 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 ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeUidInterfaceRules(final int[] uids) {
+ final int err = native_removeUidInterfaceRules(uids);
+ if (err != 0) {
+ throw new ServiceSpecificException(err, "Unable to remove uid interface rules: "
+ + Os.strerror(err));
+ }
+ }
+
+ /**
+ * 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() {
+ final int err = native_swapActiveStatsMap();
+ if (err != 0) {
+ throw new ServiceSpecificException(err, "Unable to swap active stats map: "
+ + Os.strerror(err));
+ }
+ }
+
+ /**
+ * 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 permission, final int[] uids) {
+ native_setPermissionForUids(permission, uids);
+ }
+
+ /**
+ * Set counter set for uid
+ *
+ * @param counterSet either SET_DEFAULT or SET_FOREGROUND
+ * @param uid uid to foreground/background
+ */
+ public int 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;
+ }
+
+ /**
+ * Reset Uid stats
+ * @param tag default 0
+ * @param uid given uid to be clear
+ */
+ public int 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;
+ }
+
+ private static native void native_init();
+ private native int native_addNaughtyApp(int uid);
+ private native int native_removeNaughtyApp(int uid);
+ private native int native_addNiceApp(int uid);
+ private native int native_removeNiceApp(int uid);
+ private native int native_setChildChain(int childChain, boolean enable);
+ private native int native_replaceUidChain(String name, boolean isAllowlist, int[] uids);
+ private native int native_setUidRule(int childChain, int uid, int firewallRule);
+ private native int native_addUidInterfaceRules(String ifName, int[] uids);
+ private native int native_removeUidInterfaceRules(int[] uids);
+ private native int native_swapActiveStatsMap();
+ private native void native_setPermissionForUids(int permission, int[] uids);
+ private native int native_setCounterSet(int counterSet, int uid);
+ private native int native_deleteTagData(int tag, int uid);
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index f89141d..145eade 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -73,6 +73,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -98,6 +100,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TargetApi;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
@@ -165,6 +168,7 @@
import android.net.NetworkWatchlistManager;
import android.net.OemNetworkPreferences;
import android.net.PrivateDnsConfigParcel;
+import android.net.ProfileNetworkPreference;
import android.net.ProxyInfo;
import android.net.QosCallbackException;
import android.net.QosFilter;
@@ -257,9 +261,10 @@
import com.android.server.connectivity.NetworkOffer;
import com.android.server.connectivity.NetworkRanker;
import com.android.server.connectivity.PermissionMonitor;
-import com.android.server.connectivity.ProfileNetworkPreferences;
+import com.android.server.connectivity.ProfileNetworkPreferenceList;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
+import com.android.server.connectivity.UidRangeUtils;
import libcore.io.IoUtils;
@@ -853,6 +858,9 @@
mTypeLists = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
}
+ // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is
+ // addressed.
+ @TargetApi(Build.VERSION_CODES.S)
public void loadSupportedTypes(@NonNull Context ctx, @NonNull TelephonyManager tm) {
final PackageManager pm = ctx.getPackageManager();
if (pm.hasSystemFeature(FEATURE_WIFI)) {
@@ -1484,16 +1492,17 @@
}
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
- return createDefaultNetworkCapabilitiesForUidRange(new UidRange(uid, uid));
+ return createDefaultNetworkCapabilitiesForUidRangeSet(Collections.singleton(
+ new UidRange(uid, uid)));
}
- private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRange(
- @NonNull final UidRange uids) {
+ private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRangeSet(
+ @NonNull final Set<UidRange> uidRangeSet) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
- netCap.setUids(UidRange.toIntRanges(Collections.singleton(uids)));
+ netCap.setUids(UidRange.toIntRanges(uidRangeSet));
return netCap;
}
@@ -2781,6 +2790,8 @@
sendStickyBroadcast(makeGeneralIntent(info, bcastType));
}
+ // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
+ @TargetApi(Build.VERSION_CODES.S)
private void sendStickyBroadcast(Intent intent) {
synchronized (this) {
if (!mSystemReady
@@ -4035,11 +4046,11 @@
config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.VIRTUAL,
INetd.PERMISSION_NONE,
(nai.networkAgentConfig == null || !nai.networkAgentConfig.allowBypass),
- getVpnType(nai));
+ getVpnType(nai), nai.networkAgentConfig.excludeLocalRouteVpn);
} else {
config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.PHYSICAL,
getNetworkPermission(nai.networkCapabilities), /*secure=*/ false,
- VpnManager.TYPE_VPN_NONE);
+ VpnManager.TYPE_VPN_NONE, /*excludeLocalRoutes=*/ false);
}
mNetd.networkCreate(config);
mDnsResolver.createNetworkCache(nai.network.getNetId());
@@ -5040,9 +5051,10 @@
break;
}
case EVENT_SET_PROFILE_NETWORK_PREFERENCE: {
- final Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener> arg =
- (Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener>)
- msg.obj;
+ final Pair<List<ProfileNetworkPreferenceList.Preference>,
+ IOnCompleteListener> arg =
+ (Pair<List<ProfileNetworkPreferenceList.Preference>,
+ IOnCompleteListener>) msg.obj;
handleSetProfileNetworkPreference(arg.first, arg.second);
break;
}
@@ -5665,7 +5677,8 @@
private void onUserRemoved(@NonNull final UserHandle user) {
mPermissionMonitor.onUserRemoved(user);
// If there was a network preference for this user, remove it.
- handleSetProfileNetworkPreference(new ProfileNetworkPreferences.Preference(user, null),
+ handleSetProfileNetworkPreference(
+ List.of(new ProfileNetworkPreferenceList.Preference(user, null, true)),
null /* listener */);
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
@@ -6130,6 +6143,8 @@
}
}
+ // 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,
@NonNull String callingPackageName) {
final UserHandle user = UserHandle.getUserHandleForUid(callingUid);
@@ -6597,7 +6612,8 @@
// Current per-profile network preferences. This object follows the same threading rules as
// the OEM network preferences above.
@NonNull
- private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences();
+ private ProfileNetworkPreferenceList mProfileNetworkPreferences =
+ new ProfileNetworkPreferenceList();
// A set of UIDs that should use mobile data preferentially if available. This object follows
// the same threading rules as the OEM network preferences above.
@@ -7697,7 +7713,9 @@
// changed.
// TODO: Try to track the default network that apps use and only send a proxy broadcast when
// that happens to prevent false alarms.
- if (nai.isVPN() && nai.everConnected && !NetworkCapabilities.hasSameUids(prevNc, newNc)
+ final Set<UidRange> prevUids = prevNc == null ? null : prevNc.getUidRanges();
+ final Set<UidRange> newUids = newNc == null ? null : newNc.getUidRanges();
+ if (nai.isVPN() && nai.everConnected && !UidRange.hasSameUids(prevUids, newUids)
&& (nai.linkProperties.getHttpProxy() != null || isProxySetOnAnyDefaultNetwork())) {
mProxyTracker.sendProxyBroadcast();
}
@@ -10093,19 +10111,26 @@
* See the documentation for the individual preferences for a description of the supported
* behaviors.
*
- * @param profile the profile concerned.
- * @param preference the preference for this profile, as one of the PROFILE_NETWORK_PREFERENCE_*
- * constants.
+ * @param profile the user profile for whih the preference is being set.
+ * @param preferences the list of profile network preferences for the
+ * provided profile.
* @param listener an optional listener to listen for completion of the operation.
*/
@Override
- public void setProfileNetworkPreference(@NonNull final UserHandle profile,
- @ConnectivityManager.ProfileNetworkPreference final int preference,
+ public void setProfileNetworkPreferences(
+ @NonNull final UserHandle profile,
+ @NonNull List<ProfileNetworkPreference> preferences,
@Nullable final IOnCompleteListener listener) {
+ Objects.requireNonNull(preferences);
Objects.requireNonNull(profile);
+
+ if (preferences.size() == 0) {
+ preferences.add((new ProfileNetworkPreference.Builder()).build());
+ }
+
PermissionUtils.enforceNetworkStackPermission(mContext);
if (DBG) {
- log("setProfileNetworkPreference " + profile + " to " + preference);
+ log("setProfileNetworkPreferences " + profile + " to " + preferences);
}
if (profile.getIdentifier() < 0) {
throw new IllegalArgumentException("Must explicitly specify a user handle ("
@@ -10116,23 +10141,87 @@
throw new IllegalArgumentException("Profile must be a managed profile");
}
- final NetworkCapabilities nc;
- switch (preference) {
- case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
- nc = null;
- break;
- case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
- final UidRange uids = UidRange.createForUser(profile);
- nc = createDefaultNetworkCapabilitiesForUidRange(uids);
- nc.addCapability(NET_CAPABILITY_ENTERPRISE);
- nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
- break;
- default:
- throw new IllegalArgumentException(
- "Invalid preference in setProfileNetworkPreference");
+ final List<ProfileNetworkPreferenceList.Preference> preferenceList =
+ new ArrayList<ProfileNetworkPreferenceList.Preference>();
+ boolean allowFallback = true;
+ for (final ProfileNetworkPreference preference : preferences) {
+ final NetworkCapabilities nc;
+ switch (preference.getPreference()) {
+ case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
+ nc = null;
+ if (preference.getPreferenceEnterpriseId() != 0) {
+ throw new IllegalArgumentException(
+ "Invalid enterprise identifier in setProfileNetworkPreferences");
+ }
+ break;
+ case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK:
+ allowFallback = false;
+ // continue to process the enterprise preference.
+ case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
+ if (!isEnterpriseIdentifierValid(preference.getPreferenceEnterpriseId())) {
+ throw new IllegalArgumentException(
+ "Invalid enterprise identifier in setProfileNetworkPreferences");
+ }
+ final Set<UidRange> uidRangeSet =
+ getUidListToBeAppliedForNetworkPreference(profile, preference);
+ if (!isRangeAlreadyInPreferenceList(preferenceList, uidRangeSet)) {
+ nc = createDefaultNetworkCapabilitiesForUidRangeSet(uidRangeSet);
+ } else {
+ throw new IllegalArgumentException(
+ "Overlapping uid range in setProfileNetworkPreferences");
+ }
+ nc.addCapability(NET_CAPABILITY_ENTERPRISE);
+ nc.addEnterpriseId(
+ preference.getPreferenceEnterpriseId());
+ nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid preference in setProfileNetworkPreferences");
+ }
+ preferenceList.add(new ProfileNetworkPreferenceList.Preference(
+ profile, nc, allowFallback));
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE,
- new Pair<>(new ProfileNetworkPreferences.Preference(profile, nc), listener)));
+ new Pair<>(preferenceList, listener)));
+ }
+
+ private Set<UidRange> getUidListToBeAppliedForNetworkPreference(
+ @NonNull final UserHandle profile,
+ @NonNull final ProfileNetworkPreference profileNetworkPreference) {
+ final UidRange profileUids = UidRange.createForUser(profile);
+ Set<UidRange> uidRangeSet = UidRangeUtils.convertListToUidRange(
+ profileNetworkPreference.getIncludedUids());
+ if (uidRangeSet.size() > 0) {
+ if (!UidRangeUtils.isRangeSetInUidRange(profileUids, uidRangeSet)) {
+ throw new IllegalArgumentException(
+ "Allow uid range is outside the uid range of profile.");
+ }
+ } else {
+ ArraySet<UidRange> disallowUidRangeSet = UidRangeUtils.convertListToUidRange(
+ profileNetworkPreference.getExcludedUids());
+ if (disallowUidRangeSet.size() > 0) {
+ if (!UidRangeUtils.isRangeSetInUidRange(profileUids, disallowUidRangeSet)) {
+ throw new IllegalArgumentException(
+ "disallow uid range is outside the uid range of profile.");
+ }
+ uidRangeSet = UidRangeUtils.removeRangeSetFromUidRange(profileUids,
+ disallowUidRangeSet);
+ } else {
+ uidRangeSet = new ArraySet<UidRange>();
+ uidRangeSet.add(profileUids);
+ }
+ }
+ return uidRangeSet;
+ }
+
+ private boolean isEnterpriseIdentifierValid(
+ @NetworkCapabilities.EnterpriseId int identifier) {
+ if ((identifier >= NET_ENTERPRISE_ID_1)
+ && (identifier <= NET_ENTERPRISE_ID_5)) {
+ return true;
+ }
+ return false;
}
private void validateNetworkCapabilitiesOfProfileNetworkPreference(
@@ -10142,20 +10231,25 @@
}
private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
- @NonNull final ProfileNetworkPreferences prefs) {
+ @NonNull final ProfileNetworkPreferenceList prefs) {
final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
- for (final ProfileNetworkPreferences.Preference pref : prefs.preferences) {
- // The NRI for a user should be comprised of two layers:
- // - The request for the capabilities
- // - The request for the default network, for fallback. Create an image of it to
- // have the correct UIDs in it (also a request can only be part of one NRI, because
- // of lookups in 1:1 associations like mNetworkRequests).
- // Note that denying a fallback can be implemented simply by not adding the second
- // request.
+ for (final ProfileNetworkPreferenceList.Preference pref : prefs.preferences) {
+ // The NRI for a user should contain the request for capabilities.
+ // If fallback to default network is needed then NRI should include
+ // the request for the default network. Create an image of it to
+ // have the correct UIDs in it (also a request can only be part of one NRI, because
+ // of lookups in 1:1 associations like mNetworkRequests).
final ArrayList<NetworkRequest> nrs = new ArrayList<>();
nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities));
- nrs.add(createDefaultInternetRequestForTransport(
- TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
+ if (pref.allowFallback) {
+ nrs.add(createDefaultInternetRequestForTransport(
+ TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
+ }
+ if (VDBG) {
+ loge("pref.capabilities.getUids():" + UidRange.fromIntRanges(
+ pref.capabilities.getUids()));
+ }
+
setNetworkRequestUids(nrs, UidRange.fromIntRanges(pref.capabilities.getUids()));
final NetworkRequestInfo nri = new NetworkRequestInfo(Process.myUid(), nrs,
PREFERENCE_ORDER_PROFILE);
@@ -10164,12 +10258,32 @@
return result;
}
- private void handleSetProfileNetworkPreference(
- @NonNull final ProfileNetworkPreferences.Preference preference,
- @Nullable final IOnCompleteListener listener) {
- validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
+ /**
+ * Compare if the given UID range sets have the same UIDs.
+ *
+ */
+ private boolean isRangeAlreadyInPreferenceList(
+ @NonNull List<ProfileNetworkPreferenceList.Preference> preferenceList,
+ @NonNull Set<UidRange> uidRangeSet) {
+ if (uidRangeSet.size() == 0 || preferenceList.size() == 0) {
+ return false;
+ }
+ for (ProfileNetworkPreferenceList.Preference pref : preferenceList) {
+ if (UidRangeUtils.doesRangeSetOverlap(
+ UidRange.fromIntRanges(pref.capabilities.getUids()), uidRangeSet)) {
+ return true;
+ }
+ }
+ return false;
+ }
- mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
+ private void handleSetProfileNetworkPreference(
+ @NonNull final List<ProfileNetworkPreferenceList.Preference> preferenceList,
+ @Nullable final IOnCompleteListener listener) {
+ for (final ProfileNetworkPreferenceList.Preference preference : preferenceList) {
+ validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
+ mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
+ }
removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_PROFILE);
addPerAppDefaultNetworkRequests(
createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences));
@@ -10225,12 +10339,8 @@
}
private void enforceAutomotiveDevice() {
- final boolean isAutomotiveDevice =
- mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
- if (!isAutomotiveDevice) {
- throw new UnsupportedOperationException(
- "setOemNetworkPreference() is only available on automotive devices.");
- }
+ PermissionUtils.enforceSystemFeature(mContext, PackageManager.FEATURE_AUTOMOTIVE,
+ "setOemNetworkPreference() is only available on automotive devices.");
}
/**
@@ -10556,4 +10666,94 @@
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);
+ }
+ }
+
+ @Override
+ public void updateFirewallRule(final int chain, final int uid, final boolean allow) {
+ enforceNetworkStackOrSettingsPermission();
+
+ try {
+ mNetd.firewallSetUidRule(chain, uid,
+ allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void setFirewallChainEnabled(final int chain, final boolean enable) {
+ enforceNetworkStackOrSettingsPermission();
+
+ try {
+ mNetd.firewallEnableChildChain(chain, enable);
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void replaceFirewallChain(final int chain, final int[] uids) {
+ enforceNetworkStackOrSettingsPermission();
+
+ try {
+ switch (chain) {
+ case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
+ mNetd.firewallReplaceUidChain("fw_dozable", true /* isAllowList */, uids);
+ break;
+ case ConnectivityManager.FIREWALL_CHAIN_STANDBY:
+ mNetd.firewallReplaceUidChain("fw_standby", false /* isAllowList */, uids);
+ break;
+ case ConnectivityManager.FIREWALL_CHAIN_POWERSAVE:
+ mNetd.firewallReplaceUidChain("fw_powersave", true /* isAllowList */, uids);
+ break;
+ case ConnectivityManager.FIREWALL_CHAIN_RESTRICTED:
+ mNetd.firewallReplaceUidChain("fw_restricted", true /* isAllowList */, uids);
+ break;
+ default:
+ throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
+ + chain);
+ }
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void swapActiveStatsMap() {
+ enforceNetworkStackOrSettingsPermission();
+ try {
+ mNetd.trafficSwapActiveStatsMap();
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
}
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
new file mode 100644
index 0000000..c57983b
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -0,0 +1,406 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import static android.net.INetd.IF_STATE_UP;
+import static android.net.INetd.PERMISSION_SYSTEM;
+
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.InterfaceParams;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * This coordinator is responsible for providing clat relevant functionality.
+ *
+ * {@hide}
+ */
+public class ClatCoordinator {
+ private static final String TAG = ClatCoordinator.class.getSimpleName();
+
+ // Sync from external/android-clat/clatd.c
+ // 40 bytes IPv6 header - 20 bytes IPv4 header + 8 bytes fragment header.
+ @VisibleForTesting
+ static final int MTU_DELTA = 28;
+ @VisibleForTesting
+ static final int CLAT_MAX_MTU = 65536;
+
+ // This must match the interface prefix in clatd.c.
+ private static final String CLAT_PREFIX = "v4-";
+
+ // For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses
+ // in 192.0.0.0/29 (RFC 7335).
+ @VisibleForTesting
+ static final String INIT_V4ADDR_STRING = "192.0.0.4";
+ @VisibleForTesting
+ static final int INIT_V4ADDR_PREFIX_LEN = 29;
+ private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");
+
+ private static final int INVALID_IFINDEX = 0;
+ private static final int INVALID_PID = 0;
+
+ @NonNull
+ private final INetd mNetd;
+ @NonNull
+ private final Dependencies mDeps;
+ @Nullable
+ private String mIface = null;
+ @Nullable
+ private String mNat64Prefix = null;
+ @Nullable
+ private String mXlatLocalAddress4 = null;
+ @Nullable
+ private String mXlatLocalAddress6 = null;
+ private int mPid = INVALID_PID;
+
+ @VisibleForTesting
+ abstract static class Dependencies {
+ /**
+ * Get netd.
+ */
+ @NonNull
+ public abstract INetd getNetd();
+
+ /**
+ * @see ParcelFileDescriptor#adoptFd(int).
+ */
+ @NonNull
+ public ParcelFileDescriptor adoptFd(int fd) {
+ return ParcelFileDescriptor.adoptFd(fd);
+ }
+
+ /**
+ * Get interface index for a given interface.
+ */
+ public int getInterfaceIndex(String ifName) {
+ final InterfaceParams params = InterfaceParams.getByName(ifName);
+ return params != null ? params.index : INVALID_IFINDEX;
+ }
+
+ /**
+ * Create tun interface for a given interface name.
+ */
+ public int createTunInterface(@NonNull String tuniface) throws IOException {
+ return native_createTunInterface(tuniface);
+ }
+
+ /**
+ * Pick an IPv4 address for clat.
+ */
+ @NonNull
+ public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
+ throws IOException {
+ return native_selectIpv4Address(v4addr, prefixlen);
+ }
+
+ /**
+ * Generate a checksum-neutral IID.
+ */
+ @NonNull
+ public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
+ @NonNull String prefix64) throws IOException {
+ return native_generateIpv6Address(iface, v4, prefix64);
+ }
+
+ /**
+ * Detect MTU.
+ */
+ public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
+ throws IOException {
+ return native_detectMtu(platSubnet, platSuffix, mark);
+ }
+
+ /**
+ * Open packet socket.
+ */
+ public int openPacketSocket() throws IOException {
+ return native_openPacketSocket();
+ }
+
+ /**
+ * Open IPv6 raw socket and set SO_MARK.
+ */
+ public int openRawSocket6(int mark) throws IOException {
+ return native_openRawSocket6(mark);
+ }
+
+ /**
+ * Add anycast setsockopt.
+ */
+ public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex)
+ throws IOException {
+ native_addAnycastSetsockopt(sock, v6, ifindex);
+ }
+
+ /**
+ * Configure packet socket.
+ */
+ public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex)
+ throws IOException {
+ native_configurePacketSocket(sock, v6, ifindex);
+ }
+
+ /**
+ * Start clatd.
+ */
+ public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
+ @NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96,
+ @NonNull String v4, @NonNull String v6) throws IOException {
+ return native_startClatd(tunfd, readsock6, writesock6, iface, pfx96, v4, v6);
+ }
+
+ /**
+ * Stop clatd.
+ */
+ public void stopClatd(String iface, String pfx96, String v4, String v6, int pid)
+ throws IOException {
+ native_stopClatd(iface, pfx96, v4, v6, pid);
+ }
+ }
+
+ @VisibleForTesting
+ static int getFwmark(int netId) {
+ // See union Fwmark in system/netd/include/Fwmark.h
+ return (netId & 0xffff)
+ | 0x1 << 16 // protectedFromVpn: true
+ | 0x1 << 17 // explicitlySelected: true
+ | (PERMISSION_SYSTEM & 0x3) << 18;
+ }
+
+ @VisibleForTesting
+ static int adjustMtu(int mtu) {
+ // clamp to minimum ipv6 mtu - this probably cannot ever trigger
+ if (mtu < IPV6_MIN_MTU) mtu = IPV6_MIN_MTU;
+ // clamp to buffer size
+ if (mtu > CLAT_MAX_MTU) mtu = CLAT_MAX_MTU;
+ // decrease by ipv6(40) + ipv6 fragmentation header(8) vs ipv4(20) overhead of 28 bytes
+ mtu -= MTU_DELTA;
+
+ return mtu;
+ }
+
+ public ClatCoordinator(@NonNull Dependencies deps) {
+ mDeps = deps;
+ mNetd = mDeps.getNetd();
+ }
+
+ /**
+ * Start clatd for a given interface and NAT64 prefix.
+ */
+ public String clatStart(final String iface, final int netId,
+ @NonNull final IpPrefix nat64Prefix)
+ throws IOException {
+ if (mIface != null || mPid != INVALID_PID) {
+ throw new IOException("Clatd is already running on " + mIface + " (pid " + mPid + ")");
+ }
+ if (nat64Prefix.getPrefixLength() != 96) {
+ throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
+ }
+
+ // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
+ final String v4;
+ try {
+ v4 = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN);
+ } catch (IOException e) {
+ throw new IOException("no IPv4 addresses were available for clat: " + e);
+ }
+
+ // [2] Generate a checksum-neutral IID.
+ final String pfx96 = nat64Prefix.getAddress().getHostAddress();
+ final String v6;
+ try {
+ v6 = mDeps.generateIpv6Address(iface, v4, pfx96);
+ } catch (IOException e) {
+ throw new IOException("no IPv6 addresses were available for clat: " + e);
+ }
+
+ // [3] Open, configure and bring up the tun interface.
+ // Create the v4-... tun interface.
+ final String tunIface = CLAT_PREFIX + iface;
+ final ParcelFileDescriptor tunFd;
+ try {
+ tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
+ } catch (IOException e) {
+ throw new IOException("Create tun interface " + tunIface + " failed: " + e);
+ }
+
+ // disable IPv6 on it - failing to do so is not a critical error
+ try {
+ mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
+ } catch (RemoteException | ServiceSpecificException e) {
+ tunFd.close();
+ Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
+ }
+
+ // Detect ipv4 mtu.
+ final Integer fwmark = getFwmark(netId);
+ final int detectedMtu = mDeps.detectMtu(pfx96,
+ ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
+ final int mtu = adjustMtu(detectedMtu);
+ Log.i(TAG, "ipv4 mtu is " + mtu);
+
+ // TODO: add setIptablesDropRule
+
+ // Config tun interface mtu, address and bring up.
+ try {
+ mNetd.interfaceSetMtu(tunIface, mtu);
+ } catch (RemoteException | ServiceSpecificException e) {
+ tunFd.close();
+ throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
+ }
+ final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
+ ifConfig.ifName = tunIface;
+ ifConfig.ipv4Addr = v4;
+ ifConfig.prefixLength = 32;
+ ifConfig.hwAddr = "";
+ ifConfig.flags = new String[] {IF_STATE_UP};
+ try {
+ mNetd.interfaceSetCfg(ifConfig);
+ } catch (RemoteException | ServiceSpecificException e) {
+ tunFd.close();
+ throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
+ + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
+ }
+
+ // [4] Open and configure local 464xlat read/write sockets.
+ // Opens a packet socket to receive IPv6 packets in clatd.
+ final ParcelFileDescriptor readSock6;
+ try {
+ // Use a JNI call to get native file descriptor instead of Os.socket() because we would
+ // like to use ParcelFileDescriptor to manage file descriptor. But ctor
+ // ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file
+ // descriptor to initialize ParcelFileDescriptor object instead.
+ readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
+ } catch (IOException e) {
+ tunFd.close();
+ throw new IOException("Open packet socket failed: " + e);
+ }
+
+ // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
+ final ParcelFileDescriptor writeSock6;
+ try {
+ // Use a JNI call to get native file descriptor instead of Os.socket(). See above
+ // reason why we use jniOpenPacketSocket6().
+ writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
+ } catch (IOException e) {
+ tunFd.close();
+ readSock6.close();
+ throw new IOException("Open raw socket failed: " + e);
+ }
+
+ final int ifaceIndex = mDeps.getInterfaceIndex(iface);
+ if (ifaceIndex == INVALID_IFINDEX) {
+ tunFd.close();
+ readSock6.close();
+ writeSock6.close();
+ throw new IOException("Fail to get interface index for interface " + iface);
+ }
+
+ // Start translating packets to the new prefix.
+ try {
+ mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex);
+ } catch (IOException e) {
+ tunFd.close();
+ readSock6.close();
+ writeSock6.close();
+ throw new IOException("add anycast sockopt failed: " + e);
+ }
+
+ // Update our packet socket filter to reflect the new 464xlat IP address.
+ try {
+ mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex);
+ } catch (IOException e) {
+ tunFd.close();
+ readSock6.close();
+ writeSock6.close();
+ throw new IOException("configure packet socket failed: " + e);
+ }
+
+ // [5] Start clatd.
+ try {
+ mPid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
+ writeSock6.getFileDescriptor(), iface, pfx96, v4, v6);
+ mIface = iface;
+ mNat64Prefix = pfx96;
+ mXlatLocalAddress4 = v4;
+ mXlatLocalAddress6 = v6;
+ } catch (IOException e) {
+ throw new IOException("Error start clatd on " + iface + ": " + e);
+ } finally {
+ tunFd.close();
+ readSock6.close();
+ writeSock6.close();
+ }
+
+ return v6;
+ }
+
+ /**
+ * Stop clatd
+ */
+ public void clatStop() throws IOException {
+ if (mPid == INVALID_PID) {
+ throw new IOException("Clatd has not started");
+ }
+ Log.i(TAG, "Stopping clatd pid=" + mPid + " on " + mIface);
+
+ mDeps.stopClatd(mIface, mNat64Prefix, mXlatLocalAddress4, mXlatLocalAddress6, mPid);
+ // TODO: remove setIptablesDropRule
+
+ Log.i(TAG, "clatd on " + mIface + " stopped");
+
+ mIface = null;
+ mNat64Prefix = null;
+ mXlatLocalAddress4 = null;
+ mXlatLocalAddress6 = null;
+ mPid = INVALID_PID;
+ }
+
+ private static native String native_selectIpv4Address(String v4addr, int prefixlen)
+ throws IOException;
+ private static native String native_generateIpv6Address(String iface, String v4,
+ String prefix64) throws IOException;
+ private static native int native_createTunInterface(String tuniface) throws IOException;
+ private static native int native_detectMtu(String platSubnet, int platSuffix, int mark)
+ throws IOException;
+ private static native int native_openPacketSocket() throws IOException;
+ private static native int native_openRawSocket6(int mark) throws IOException;
+ private static native void native_addAnycastSetsockopt(FileDescriptor sock, String v6,
+ int ifindex) throws IOException;
+ private static native void native_configurePacketSocket(FileDescriptor sock, String v6,
+ int ifindex) throws IOException;
+ private static native int native_startClatd(FileDescriptor tunfd, FileDescriptor readsock6,
+ FileDescriptor writesock6, String iface, String pfx96, String v4, String v6)
+ throws IOException;
+ private static native void native_stopClatd(String iface, String pfx96, String v4, String v6,
+ int pid) throws IOException;
+}
diff --git a/service/src/com/android/server/connectivity/ProfileNetworkPreferences.java b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
similarity index 80%
rename from service/src/com/android/server/connectivity/ProfileNetworkPreferences.java
rename to service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
index dd2815d..71f342d 100644
--- a/service/src/com/android/server/connectivity/ProfileNetworkPreferences.java
+++ b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
@@ -30,7 +30,7 @@
*
* A given profile can only have one preference.
*/
-public class ProfileNetworkPreferences {
+public class ProfileNetworkPreferenceList {
/**
* A single preference, as it applies to a given user profile.
*/
@@ -38,26 +38,32 @@
@NonNull public final UserHandle user;
// Capabilities are only null when sending an object to remove the setting for a user
@Nullable public final NetworkCapabilities capabilities;
+ public final boolean allowFallback;
public Preference(@NonNull final UserHandle user,
- @Nullable final NetworkCapabilities capabilities) {
+ @Nullable final NetworkCapabilities capabilities,
+ final boolean allowFallback) {
this.user = user;
this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities);
+ this.allowFallback = allowFallback;
}
/** toString */
public String toString() {
- return "[ProfileNetworkPreference user=" + user + " caps=" + capabilities + "]";
+ return "[ProfileNetworkPreference user=" + user
+ + " caps=" + capabilities
+ + " allowFallback=" + allowFallback
+ + "]";
}
}
@NonNull public final List<Preference> preferences;
- public ProfileNetworkPreferences() {
+ public ProfileNetworkPreferenceList() {
preferences = Collections.EMPTY_LIST;
}
- private ProfileNetworkPreferences(@NonNull final List<Preference> list) {
+ private ProfileNetworkPreferenceList(@NonNull final List<Preference> list) {
preferences = Collections.unmodifiableList(list);
}
@@ -68,7 +74,7 @@
* preference. Passing a Preference object containing a null capabilities object is equivalent
* to (and indeed, implemented as) removing the preference for this user.
*/
- public ProfileNetworkPreferences plus(@NonNull final Preference pref) {
+ public ProfileNetworkPreferenceList plus(@NonNull final Preference pref) {
final ArrayList<Preference> newPrefs = new ArrayList<>();
for (final Preference existingPref : preferences) {
if (!existingPref.user.equals(pref.user)) {
@@ -78,7 +84,7 @@
if (null != pref.capabilities) {
newPrefs.add(pref);
}
- return new ProfileNetworkPreferences(newPrefs);
+ return new ProfileNetworkPreferenceList(newPrefs);
}
public boolean isEmpty() {
diff --git a/service/src/com/android/server/connectivity/UidRangeUtils.java b/service/src/com/android/server/connectivity/UidRangeUtils.java
new file mode 100644
index 0000000..7318296
--- /dev/null
+++ b/service/src/com/android/server/connectivity/UidRangeUtils.java
@@ -0,0 +1,156 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import android.annotation.NonNull;
+import android.net.UidRange;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Utility class for UidRange
+ *
+ * @hide
+ */
+public final class UidRangeUtils {
+ /**
+ * Check if given uid range set is within the uid range
+ * @param uids uid range in which uidRangeSet is checked to be in range.
+ * @param uidRangeSet uid range set to be be checked if it is in range of uids
+ * @return true uidRangeSet is in the range of uids
+ * @hide
+ */
+ public static boolean isRangeSetInUidRange(@NonNull UidRange uids,
+ @NonNull Set<UidRange> uidRangeSet) {
+ Objects.requireNonNull(uids);
+ Objects.requireNonNull(uidRangeSet);
+ if (uidRangeSet.size() == 0) {
+ return true;
+ }
+ for (UidRange range : uidRangeSet) {
+ if (!uids.contains(range.start) || !uids.contains(range.stop)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Remove given uid ranges set from a uid range
+ * @param uids uid range from which uidRangeSet will be removed
+ * @param uidRangeSet uid range set to be removed from uids.
+ * WARNING : This function requires the UidRanges in uidRangeSet to be disjoint
+ * WARNING : This function requires the arrayset to be iterated in increasing order of the
+ * ranges. Today this is provided by the iteration order stability of
+ * ArraySet, and the fact that the code creating this ArraySet always
+ * creates it in increasing order.
+ * Note : if any of the above is not satisfied this function throws IllegalArgumentException
+ * TODO : remove these limitations
+ * @hide
+ */
+ public static ArraySet<UidRange> removeRangeSetFromUidRange(@NonNull UidRange uids,
+ @NonNull ArraySet<UidRange> uidRangeSet) {
+ Objects.requireNonNull(uids);
+ Objects.requireNonNull(uidRangeSet);
+ final ArraySet<UidRange> filteredRangeSet = new ArraySet<UidRange>();
+ if (uidRangeSet.size() == 0) {
+ filteredRangeSet.add(uids);
+ return filteredRangeSet;
+ }
+
+ int start = uids.start;
+ UidRange previousRange = null;
+ for (UidRange uidRange : uidRangeSet) {
+ if (previousRange != null) {
+ if (previousRange.stop > uidRange.start) {
+ throw new IllegalArgumentException("UID ranges are not increasing order");
+ }
+ }
+ if (uidRange.start > start) {
+ filteredRangeSet.add(new UidRange(start, uidRange.start - 1));
+ start = uidRange.stop + 1;
+ } else if (uidRange.start == start) {
+ start = uidRange.stop + 1;
+ }
+ previousRange = uidRange;
+ }
+ if (start < uids.stop) {
+ filteredRangeSet.add(new UidRange(start, uids.stop));
+ }
+ return filteredRangeSet;
+ }
+
+ /**
+ * Compare if the given UID range sets have overlapping uids
+ * @param uidRangeSet1 first uid range set to check for overlap
+ * @param uidRangeSet2 second uid range set to check for overlap
+ * @hide
+ */
+ public static boolean doesRangeSetOverlap(@NonNull Set<UidRange> uidRangeSet1,
+ @NonNull Set<UidRange> uidRangeSet2) {
+ Objects.requireNonNull(uidRangeSet1);
+ Objects.requireNonNull(uidRangeSet2);
+
+ if (uidRangeSet1.size() == 0 || uidRangeSet2.size() == 0) {
+ return false;
+ }
+ for (UidRange range1 : uidRangeSet1) {
+ for (UidRange range2 : uidRangeSet2) {
+ if (range1.contains(range2.start) || range1.contains(range2.stop)
+ || range2.contains(range1.start) || range2.contains(range1.stop)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Convert a list of uid to set of UidRanges.
+ * @param uids list of uids
+ * @return set of UidRanges
+ * @hide
+ */
+ public static ArraySet<UidRange> convertListToUidRange(@NonNull List<Integer> uids) {
+ Objects.requireNonNull(uids);
+ final ArraySet<UidRange> uidRangeSet = new ArraySet<UidRange>();
+ if (uids.size() == 0) {
+ return uidRangeSet;
+ }
+ List<Integer> uidsNew = new ArrayList<>(uids);
+ Collections.sort(uidsNew);
+ int start = uidsNew.get(0);
+ int stop = start;
+
+ for (Integer i : uidsNew) {
+ if (i <= stop + 1) {
+ stop = i;
+ } else {
+ uidRangeSet.add(new UidRange(start, stop));
+ start = i;
+ stop = i;
+ }
+ }
+ uidRangeSet.add(new UidRange(start, stop));
+ return uidRangeSet;
+ }
+}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index f47f6b0..acf04bf 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -64,8 +64,7 @@
name: "ConnectivityCoverageTests",
// Tethering started on SDK 30
min_sdk_version: "30",
- // TODO: change to 31 as soon as it is available
- target_sdk_version: "30",
+ target_sdk_version: "31",
test_suites: ["general-tests", "mts-tethering"],
defaults: [
"framework-connectivity-test-defaults",
@@ -115,6 +114,7 @@
// meaning @hide APIs in framework-connectivity are resolved before @SystemApi
// stubs in framework
"framework-connectivity.impl",
+ "framework-connectivity-tiramisu.impl",
"framework-tethering.impl",
"framework",
@@ -122,3 +122,25 @@
"framework-res",
],
}
+
+// Defaults for tests that want to run in mainline-presubmit.
+// Not widely used because many of our tests have AndroidTest.xml files and
+// use the mainline-param config-descriptor metadata in AndroidTest.xml.
+
+// test_mainline_modules is an array of strings. Each element in the array is a list of modules
+// separated by "+". The modules in this list must be in alphabetical order.
+// See SuiteModuleLoader.java.
+// TODO: why are the modules separated by + instead of being separate entries in the array?
+mainline_presubmit_modules = [
+ "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex",
+]
+
+cc_defaults {
+ name: "connectivity-mainline-presubmit-cc-defaults",
+ test_mainline_modules: mainline_presubmit_modules,
+}
+
+java_defaults {
+ name: "connectivity-mainline-presubmit-java-defaults",
+ test_mainline_modules: mainline_presubmit_modules,
+}
diff --git a/tests/common/java/ParseExceptionTest.kt b/tests/common/java/ParseExceptionTest.kt
index b702d61..ca01c76 100644
--- a/tests/common/java/ParseExceptionTest.kt
+++ b/tests/common/java/ParseExceptionTest.kt
@@ -18,6 +18,7 @@
import android.os.Build
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNull
@@ -27,6 +28,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@ConnectivityModuleTest
class ParseExceptionTest {
@get:Rule
val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.R)
diff --git a/tests/common/java/android/net/CaptivePortalDataTest.kt b/tests/common/java/android/net/CaptivePortalDataTest.kt
index 18a9331..f927380 100644
--- a/tests/common/java/android/net/CaptivePortalDataTest.kt
+++ b/tests/common/java/android/net/CaptivePortalDataTest.kt
@@ -19,7 +19,6 @@
import android.os.Build
import androidx.test.filters.SmallTest
import com.android.modules.utils.build.SdkLevel
-import com.android.testutils.assertParcelSane
import com.android.testutils.assertParcelingIsLossless
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -71,9 +70,8 @@
@Test
fun testParcelUnparcel() {
- val fieldCount = if (SdkLevel.isAtLeastS()) 10 else 7
- assertParcelSane(data, fieldCount)
- assertParcelSane(dataFromPasspoint, fieldCount)
+ assertParcelingIsLossless(data)
+ assertParcelingIsLossless(dataFromPasspoint)
assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
diff --git a/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 294ed10..03a9a80 100644
--- a/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -21,7 +21,7 @@
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -202,7 +202,7 @@
@Test
public void testConnectivityReportParcelUnparcel() {
- assertParcelSane(createSampleConnectivityReport(), 5);
+ assertParcelingIsLossless(createSampleConnectivityReport());
}
private DataStallReport createSampleDataStallReport() {
@@ -303,7 +303,7 @@
@Test
public void testDataStallReportParcelUnparcel() {
- assertParcelSane(createSampleDataStallReport(), 6);
+ assertParcelingIsLossless(createSampleDataStallReport());
}
@Test
diff --git a/tests/common/java/android/net/DhcpInfoTest.java b/tests/common/java/android/net/DhcpInfoTest.java
index ab4726b..b42e183 100644
--- a/tests/common/java/android/net/DhcpInfoTest.java
+++ b/tests/common/java/android/net/DhcpInfoTest.java
@@ -17,7 +17,6 @@
package android.net;
import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTL;
-import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
import static org.junit.Assert.assertEquals;
@@ -101,7 +100,6 @@
// Cannot use assertParcelSane() here because this requires .equals() to work as
// defined, but DhcpInfo has a different legacy behavior that we cannot change.
final DhcpInfo dhcpInfo = createDhcpInfoObject();
- assertFieldCountEquals(7, DhcpInfo.class);
final DhcpInfo dhcpInfoRoundTrip = parcelingRoundTrip(dhcpInfo);
assertTrue(dhcpInfoEquals(null, null));
diff --git a/tests/common/java/android/net/IpPrefixTest.java b/tests/common/java/android/net/IpPrefixTest.java
index 50ecb42..fef6416 100644
--- a/tests/common/java/android/net/IpPrefixTest.java
+++ b/tests/common/java/android/net/IpPrefixTest.java
@@ -17,7 +17,6 @@
package android.net;
import static com.android.testutils.MiscAsserts.assertEqualBothWays;
-import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
@@ -31,6 +30,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.ConnectivityModuleTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,6 +40,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@ConnectivityModuleTest
public class IpPrefixTest {
private static InetAddress address(String addr) {
@@ -122,6 +124,9 @@
p = new IpPrefix("[2001:db8::123]/64");
assertEquals("2001:db8::/64", p.toString());
+
+ p = new IpPrefix(InetAddresses.parseNumericAddress("::128"), 64);
+ assertEquals("::/64", p.toString());
}
@Test
@@ -368,7 +373,5 @@
p = new IpPrefix("192.0.2.0/25");
assertParcelingIsLossless(p);
assertTrue(p.isIPv4());
-
- assertFieldCountEquals(2, IpPrefix.class);
}
}
diff --git a/tests/common/java/android/net/LinkAddressTest.java b/tests/common/java/android/net/LinkAddressTest.java
index 2cf3cf9..6b04fee 100644
--- a/tests/common/java/android/net/LinkAddressTest.java
+++ b/tests/common/java/android/net/LinkAddressTest.java
@@ -28,7 +28,6 @@
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static com.android.testutils.MiscAsserts.assertEqualBothWays;
-import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
@@ -44,8 +43,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.Rule;
@@ -63,6 +62,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@ConnectivityModuleTest
public class LinkAddressTest {
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -352,17 +352,6 @@
assertParcelingIsLossless(l);
}
- @Test @IgnoreAfter(Build.VERSION_CODES.Q)
- public void testFieldCount_Q() {
- assertFieldCountEquals(4, LinkAddress.class);
- }
-
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
- public void testFieldCount() {
- // Make sure any new field is covered by the above parceling tests when changing this number
- assertFieldCountEquals(6, LinkAddress.class);
- }
-
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testDeprecationTime() {
try {
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 550953d..4d85a57 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -20,7 +20,6 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.RouteInfo.RTN_UNREACHABLE;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
@@ -41,6 +40,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
+import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -60,6 +60,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@ConnectivityModuleTest
public class LinkPropertiesTest {
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -1006,7 +1007,7 @@
@Test @IgnoreAfter(Build.VERSION_CODES.Q)
public void testLinkPropertiesParcelable_Q() throws Exception {
final LinkProperties source = makeLinkPropertiesForParceling();
- assertParcelSane(source, 14 /* fieldCount */);
+ assertParcelingIsLossless(source);
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
@@ -1017,8 +1018,7 @@
source.setCaptivePortalApiUrl(CAPPORT_API_URL);
source.setCaptivePortalData((CaptivePortalData) getCaptivePortalData());
source.setDhcpServerAddress((Inet4Address) GATEWAY1);
- assertParcelSane(new LinkProperties(source, true /* parcelSensitiveFields */),
- 18 /* fieldCount */);
+ assertParcelingIsLossless(new LinkProperties(source, true /* parcelSensitiveFields */));
// Verify that without using a sensitiveFieldsParcelingCopy, sensitive fields are cleared.
final LinkProperties sanitized = new LinkProperties(source);
diff --git a/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt b/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt
index a5e44d5..4a4859d 100644
--- a/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt
+++ b/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt
@@ -22,14 +22,11 @@
import android.os.Build
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-
-import com.android.testutils.assertParcelSane
+import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-
-import java.lang.IllegalStateException
-
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertFalse
import org.junit.Rule
import org.junit.Test
@@ -38,6 +35,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
+@ConnectivityModuleTest
class MatchAllNetworkSpecifierTest {
@Rule @JvmField
val ignoreRule: DevSdkIgnoreRule = DevSdkIgnoreRule()
@@ -50,7 +48,7 @@
@Test
fun testParcel() {
- assertParcelSane(MatchAllNetworkSpecifier(), 0)
+ assertParcelingIsLossless(MatchAllNetworkSpecifier())
}
@Test
diff --git a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
index 46f39dd..ad7a526 100644
--- a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
@@ -23,10 +23,9 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertEqualBothWays
-import com.android.testutils.assertFieldCountEquals
-import com.android.testutils.assertParcelSane
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.assertParcelingIsLossless
import com.android.testutils.parcelingRoundTrip
import java.net.InetAddress
import org.junit.Assert.assertEquals
@@ -93,7 +92,7 @@
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testParcel() {
- assertParcelSane(nattKeepalivePacket(), 0)
+ assertParcelingIsLossless(nattKeepalivePacket())
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
@@ -103,8 +102,6 @@
assertNotEquals(nattKeepalivePacket(srcAddress = TEST_DST_ADDRV4), nattKeepalivePacket())
// Test src port only because dst port have to be NATT_PORT
assertNotEquals(nattKeepalivePacket(srcPort = TEST_PORT2), nattKeepalivePacket())
- // Make sure the parceling test is updated if fields are added in the base class.
- assertFieldCountEquals(5, KeepalivePacketData::class.java)
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt
index afaae1c..b339a27 100644
--- a/tests/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt
@@ -20,9 +20,10 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.modules.utils.build.SdkLevel.isAtLeastS
+import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -32,6 +33,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
+@ConnectivityModuleTest
class NetworkAgentConfigTest {
@Rule @JvmField
val ignoreRule = DevSdkIgnoreRule()
@@ -48,13 +50,7 @@
setBypassableVpn(true)
}
}.build()
- if (isAtLeastS()) {
- // From S, the config will have 12 items
- assertParcelSane(config, 12)
- } else {
- // For R or below, the config will have 10 items
- assertParcelSane(config, 10)
- }
+ assertParcelingIsLossless(config)
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 27a3cc2..bea00a9 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -22,6 +22,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
@@ -33,9 +34,16 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_2;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_4;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -51,9 +59,9 @@
import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.MiscAsserts.assertEmpty;
import static com.android.testutils.MiscAsserts.assertThrows;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static org.junit.Assert.assertArrayEquals;
@@ -285,19 +293,11 @@
assertFalse(netCap2.satisfiedByUids(netCap));
assertFalse(netCap.appliesToUid(650));
assertTrue(netCap2.appliesToUid(650));
- netCap.combineCapabilities(netCap2);
+ netCap.setUids(uids);
assertTrue(netCap2.satisfiedByUids(netCap));
assertTrue(netCap.appliesToUid(650));
assertFalse(netCap.appliesToUid(500));
- assertTrue(new NetworkCapabilities().satisfiedByUids(netCap));
- netCap.combineCapabilities(new NetworkCapabilities());
- assertTrue(netCap.appliesToUid(500));
- assertTrue(netCap.appliesToUidRange(new UidRange(1, 100000)));
- assertFalse(netCap2.appliesToUid(500));
- assertFalse(netCap2.appliesToUidRange(new UidRange(1, 100000)));
- assertTrue(new NetworkCapabilities().satisfiedByUids(netCap));
-
// Null uids satisfies everything.
netCap.setUids(null);
assertTrue(netCap2.satisfiedByUids(netCap));
@@ -346,21 +346,7 @@
}
private void testParcelSane(NetworkCapabilities cap) {
- // This test can be run as unit test against the latest system image, as CTS to verify
- // an Android release that is as recent as the test, or as MTS to verify the
- // Connectivity module. In the first two cases NetworkCapabilities will be as recent
- // as the test. In the last case, starting from S NetworkCapabilities is updated as part
- // of Connectivity, so it is also as recent as the test. For MTS on Q and R,
- // NetworkCapabilities is not updatable, so it may have a different number of fields.
- if (isAtLeastS()) {
- // When this test is run on S+, NetworkCapabilities is as recent as the test,
- // so this should be the most recent known number of fields.
- assertParcelSane(cap, 17);
- } else if (isAtLeastR()) {
- assertParcelSane(cap, 15);
- } else {
- assertParcelSane(cap, 11);
- }
+ assertParcelingIsLossless(cap);
}
private static NetworkCapabilities createNetworkCapabilitiesWithTransportInfo() {
@@ -431,6 +417,31 @@
assertFalse(nr.satisfiedByNetworkCapabilities(new NetworkCapabilities()));
}
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+ public void testPrioritizeLatencyAndBandwidth() {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_LATENCY);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_LATENCY);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ }
+
@Test @IgnoreUpTo(Build.VERSION_CODES.R)
public void testOemPrivate() {
NetworkCapabilities nc = new NetworkCapabilities();
@@ -590,103 +601,6 @@
}
@Test
- public void testCombineCapabilities() {
- NetworkCapabilities nc1 = new NetworkCapabilities();
- NetworkCapabilities nc2 = new NetworkCapabilities();
-
- if (isAtLeastS()) {
- nc1.addForbiddenCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
- }
- nc1.addCapability(NET_CAPABILITY_NOT_ROAMING);
- assertNotEquals(nc1, nc2);
- nc2.combineCapabilities(nc1);
- assertEquals(nc1, nc2);
- assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
- if (isAtLeastS()) {
- assertTrue(nc2.hasForbiddenCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
- }
-
- if (isAtLeastS()) {
- // This will effectively move NOT_ROAMING capability from required to forbidden for nc1.
- nc1.addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING);
- // It is not allowed to have the same capability in both wanted and forbidden list.
- assertThrows(IllegalArgumentException.class, () -> nc2.combineCapabilities(nc1));
- // Remove forbidden capability to continue other tests.
- nc1.removeForbiddenCapability(NET_CAPABILITY_NOT_ROAMING);
- }
-
- nc1.setSSID(TEST_SSID);
- nc2.combineCapabilities(nc1);
- if (isAtLeastR()) {
- assertTrue(TEST_SSID.equals(nc2.getSsid()));
- }
-
- // Because they now have the same SSID, the following call should not throw
- nc2.combineCapabilities(nc1);
-
- nc1.setSSID(DIFFERENT_TEST_SSID);
- try {
- nc2.combineCapabilities(nc1);
- fail("Expected IllegalStateException: can't combine different SSIDs");
- } catch (IllegalStateException expected) {}
- nc1.setSSID(TEST_SSID);
-
- if (isAtLeastS()) {
- nc1.setUids(uidRanges(10, 13));
- assertNotEquals(nc1, nc2);
- nc2.combineCapabilities(nc1); // Everything + 10~13 is still everything.
- assertNotEquals(nc1, nc2);
- nc1.combineCapabilities(nc2); // 10~13 + everything is everything.
- assertEquals(nc1, nc2);
- nc1.setUids(uidRanges(10, 13));
- nc2.setUids(uidRanges(20, 23));
- assertNotEquals(nc1, nc2);
- nc1.combineCapabilities(nc2);
- assertTrue(nc1.appliesToUid(12));
- assertFalse(nc2.appliesToUid(12));
- assertTrue(nc1.appliesToUid(22));
- assertTrue(nc2.appliesToUid(22));
-
- // Verify the subscription id list can be combined only when they are equal.
- nc1.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2));
- nc2.setSubscriptionIds(Set.of(TEST_SUBID2));
- assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1));
-
- nc2.setSubscriptionIds(Set.of());
- assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1));
-
- nc2.setSubscriptionIds(Set.of(TEST_SUBID2, TEST_SUBID1));
- nc2.combineCapabilities(nc1);
- assertEquals(Set.of(TEST_SUBID2, TEST_SUBID1), nc2.getSubscriptionIds());
- }
- }
-
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
- public void testCombineCapabilities_AdministratorUids() {
- final NetworkCapabilities nc1 = new NetworkCapabilities();
- final NetworkCapabilities nc2 = new NetworkCapabilities();
-
- final int[] adminUids = {3, 6, 12};
- nc1.setAdministratorUids(adminUids);
- nc2.combineCapabilities(nc1);
- assertTrue(nc2.equalsAdministratorUids(nc1));
- assertArrayEquals(nc2.getAdministratorUids(), adminUids);
-
- final int[] adminUidsOtherOrder = {3, 12, 6};
- nc1.setAdministratorUids(adminUidsOtherOrder);
- assertTrue(nc2.equalsAdministratorUids(nc1));
-
- final int[] adminUids2 = {11, 1, 12, 3, 6};
- nc1.setAdministratorUids(adminUids2);
- assertFalse(nc2.equalsAdministratorUids(nc1));
- assertFalse(Arrays.equals(nc2.getAdministratorUids(), adminUids2));
- try {
- nc2.combineCapabilities(nc1);
- fail("Shouldn't be able to combine different lists of admin UIDs");
- } catch (IllegalStateException expected) { }
- }
-
- @Test
public void testSetCapabilities() {
final int[] REQUIRED_CAPABILITIES = new int[] {
NET_CAPABILITY_INTERNET, NET_CAPABILITY_NOT_VPN };
@@ -802,29 +716,6 @@
}
@Test
- public void testCombineTransportInfo() {
- NetworkCapabilities nc1 = new NetworkCapabilities();
- nc1.setTransportInfo(new TestTransportInfo());
-
- NetworkCapabilities nc2 = new NetworkCapabilities();
- // new TransportInfo so that object is not #equals to nc1's TransportInfo (that's where
- // combine fails)
- nc2.setTransportInfo(new TestTransportInfo());
-
- try {
- nc1.combineCapabilities(nc2);
- fail("Should not be able to combine NetworkCabilities which contain TransportInfos");
- } catch (IllegalStateException expected) {
- // empty
- }
-
- // verify that can combine with identical TransportInfo objects
- NetworkCapabilities nc3 = new NetworkCapabilities();
- nc3.setTransportInfo(nc1.getTransportInfo());
- nc1.combineCapabilities(nc3);
- }
-
- @Test
public void testSet() {
NetworkCapabilities nc1 = new NetworkCapabilities();
NetworkCapabilities nc2 = new NetworkCapabilities();
@@ -925,6 +816,88 @@
} catch (IllegalStateException expected) { }
}
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+ public void testEnterpriseId() {
+ final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_ENTERPRISE)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .build();
+ assertEquals(1, nc1.getEnterpriseIds().length);
+ assertEquals(NET_ENTERPRISE_ID_1,
+ nc1.getEnterpriseIds()[0]);
+ final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_ENTERPRISE)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .build();
+ assertEquals(2, nc2.getEnterpriseIds().length);
+ assertEquals(NET_ENTERPRISE_ID_1,
+ nc2.getEnterpriseIds()[0]);
+ assertEquals(NET_ENTERPRISE_ID_2,
+ nc2.getEnterpriseIds()[1]);
+ final NetworkCapabilities nc3 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_ENTERPRISE)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .addEnterpriseId(NET_ENTERPRISE_ID_3)
+ .addEnterpriseId(NET_ENTERPRISE_ID_4)
+ .addEnterpriseId(NET_ENTERPRISE_ID_5)
+ .build();
+ assertEquals(5, nc3.getEnterpriseIds().length);
+ assertEquals(NET_ENTERPRISE_ID_1,
+ nc3.getEnterpriseIds()[0]);
+ assertEquals(NET_ENTERPRISE_ID_2,
+ nc3.getEnterpriseIds()[1]);
+ assertEquals(NET_ENTERPRISE_ID_3,
+ nc3.getEnterpriseIds()[2]);
+ assertEquals(NET_ENTERPRISE_ID_4,
+ nc3.getEnterpriseIds()[3]);
+ assertEquals(NET_ENTERPRISE_ID_5,
+ nc3.getEnterpriseIds()[4]);
+
+ final Class<IllegalArgumentException> illegalArgumentExceptionClass =
+ IllegalArgumentException.class;
+ assertThrows(illegalArgumentExceptionClass, () -> new NetworkCapabilities.Builder()
+ .addEnterpriseId(6)
+ .build());
+ assertThrows(illegalArgumentExceptionClass, () -> new NetworkCapabilities.Builder()
+ .removeEnterpriseId(6)
+ .build());
+
+ final Class<IllegalStateException> illegalStateException =
+ IllegalStateException.class;
+ assertThrows(illegalStateException, () -> new NetworkCapabilities.Builder()
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .build());
+
+ final NetworkCapabilities nc4 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_ENTERPRISE)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_1)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_2)
+ .build();
+ assertEquals(1, nc4.getEnterpriseIds().length);
+ assertTrue(nc4.hasEnterpriseId(NET_ENTERPRISE_ID_1));
+
+ final NetworkCapabilities nc5 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_CBS)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_1)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_2)
+ .build();
+
+ assertTrue(nc4.satisfiedByNetworkCapabilities(nc1));
+ assertTrue(nc1.satisfiedByNetworkCapabilities(nc4));
+
+ assertFalse(nc3.satisfiedByNetworkCapabilities(nc2));
+ assertTrue(nc2.satisfiedByNetworkCapabilities(nc3));
+
+ assertFalse(nc1.satisfiedByNetworkCapabilities(nc5));
+ assertFalse(nc5.satisfiedByNetworkCapabilities(nc1));
+ }
+
@Test
public void testWifiAwareNetworkSpecifier() {
final NetworkCapabilities nc = new NetworkCapabilities()
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index 626a344..3ceacf8 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -32,6 +32,7 @@
import androidx.test.InstrumentationRegistry
import com.android.net.module.util.ArrayTrackRecord
import com.android.testutils.CompatUtil
+import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -40,7 +41,6 @@
import com.android.testutils.isDevSdkInRange
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,6 +63,7 @@
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.Q)
+@ConnectivityModuleTest
class NetworkProviderTest {
@Rule @JvmField
val mIgnoreRule = DevSdkIgnoreRule()
@@ -205,7 +206,6 @@
}
}
- @Ignore("Temporarily disable the test since prebuilt Connectivity module is not updated.")
@IgnoreUpTo(Build.VERSION_CODES.R)
@Test
fun testRegisterNetworkOffer() {
diff --git a/tests/common/java/android/net/NetworkSpecifierTest.kt b/tests/common/java/android/net/NetworkSpecifierTest.kt
index f3409f5..b960417 100644
--- a/tests/common/java/android/net/NetworkSpecifierTest.kt
+++ b/tests/common/java/android/net/NetworkSpecifierTest.kt
@@ -17,18 +17,20 @@
import android.os.Build
import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
-import org.junit.Test
-import org.junit.runner.RunWith
+import kotlin.test.assertTrue
@SmallTest
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.Q)
+@ConnectivityModuleTest
class NetworkSpecifierTest {
private class TestNetworkSpecifier(
val intData: Int = 123,
diff --git a/tests/common/java/android/net/NetworkStateSnapshotTest.kt b/tests/common/java/android/net/NetworkStateSnapshotTest.kt
index 0ca4d95..0dad6a8 100644
--- a/tests/common/java/android/net/NetworkStateSnapshotTest.kt
+++ b/tests/common/java/android/net/NetworkStateSnapshotTest.kt
@@ -22,9 +22,10 @@
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.os.Build
import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Test
import org.junit.runner.RunWith
import java.net.Inet4Address
@@ -59,6 +60,7 @@
@SmallTest
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@ConnectivityModuleTest
class NetworkStateSnapshotTest {
@Test
@@ -67,7 +69,7 @@
LinkProperties(), null, TYPE_NONE)
val snapshot = NetworkStateSnapshot(
Network(TEST_NETID), TEST_CAPABILITIES, TEST_LINK_PROPERTIES, TEST_IMSI, TYPE_WIFI)
- assertParcelSane(emptySnapshot, 5)
- assertParcelSane(snapshot, 5)
+ assertParcelingIsLossless(emptySnapshot)
+ assertParcelingIsLossless(snapshot)
}
}
diff --git a/tests/common/java/android/net/NetworkTest.java b/tests/common/java/android/net/NetworkTest.java
index 7423c73..c102cb3 100644
--- a/tests/common/java/android/net/NetworkTest.java
+++ b/tests/common/java/android/net/NetworkTest.java
@@ -28,6 +28,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -46,6 +47,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@ConnectivityModuleTest
public class NetworkTest {
final Network mNetwork = new Network(99);
diff --git a/tests/common/java/android/net/OemNetworkPreferencesTest.java b/tests/common/java/android/net/OemNetworkPreferencesTest.java
index fd29a95..d96f80c 100644
--- a/tests/common/java/android/net/OemNetworkPreferencesTest.java
+++ b/tests/common/java/android/net/OemNetworkPreferencesTest.java
@@ -17,7 +17,7 @@
package android.net;
import static com.android.testutils.MiscAsserts.assertThrows;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -27,6 +27,7 @@
import androidx.test.filters.SmallTest;
+import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -38,6 +39,7 @@
@IgnoreUpTo(Build.VERSION_CODES.R)
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
+@ConnectivityModuleTest
public class OemNetworkPreferencesTest {
private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
@@ -101,7 +103,7 @@
final OemNetworkPreferences prefs = mBuilder.build();
- assertParcelSane(prefs, 1 /* fieldCount */);
+ assertParcelingIsLossless(prefs);
}
@Test
diff --git a/tests/common/java/android/net/RouteInfoTest.java b/tests/common/java/android/net/RouteInfoTest.java
index 71689f9..5b28b84 100644
--- a/tests/common/java/android/net/RouteInfoTest.java
+++ b/tests/common/java/android/net/RouteInfoTest.java
@@ -16,10 +16,11 @@
package android.net;
+import static android.net.RouteInfo.RTN_THROW;
+import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.RouteInfo.RTN_UNREACHABLE;
import static com.android.testutils.MiscAsserts.assertEqualBothWays;
-import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
@@ -36,8 +37,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.Rule;
@@ -50,6 +51,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@ConnectivityModuleTest
public class RouteInfoTest {
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -329,6 +331,16 @@
}
@Test
+ public void testRouteTypes() {
+ RouteInfo r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE);
+ assertEquals(RTN_UNREACHABLE, r.getType());
+ r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNICAST);
+ assertEquals(RTN_UNICAST, r.getType());
+ r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_THROW);
+ assertEquals(RTN_THROW, r.getType());
+ }
+
+ @Test
public void testTruncation() {
LinkAddress l;
RouteInfo r;
@@ -371,17 +383,6 @@
assertParcelingIsLossless(r);
}
- @Test @IgnoreAfter(Build.VERSION_CODES.Q)
- public void testFieldCount_Q() {
- assertFieldCountEquals(6, RouteInfo.class);
- }
-
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
- public void testFieldCount() {
- // Make sure any new field is covered by the above parceling tests when changing this number
- assertFieldCountEquals(7, RouteInfo.class);
- }
-
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testMtu() {
RouteInfo r;
diff --git a/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt b/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt
index 7a18bb0..063ea23 100644
--- a/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt
@@ -20,8 +20,7 @@
import android.os.Build
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.assertFieldCountEquals
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Test
import org.junit.runner.RunWith
import java.net.InetAddress
@@ -68,15 +67,11 @@
assertNotEquals(makeData(tcpWndScale = 3), makeData())
assertNotEquals(makeData(ipTos = 0x14), makeData())
assertNotEquals(makeData(ipTtl = 11), makeData())
-
- // Update above assertions if field is added
- assertFieldCountEquals(5, KeepalivePacketData::class.java)
- assertFieldCountEquals(6, TcpKeepalivePacketData::class.java)
}
@Test
fun testParcelUnparcel() {
- assertParcelSane(makeData(), fieldCount = 6) { a, b ->
+ assertParcelingIsLossless(makeData()) { a, b ->
// .equals() does not verify .packet
a == b && a.packet contentEquals b.packet
}
@@ -98,9 +93,5 @@
assertTrue(str.contains(data.getTcpWindowScale().toString()))
assertTrue(str.contains(data.getIpTos().toString()))
assertTrue(str.contains(data.getIpTtl().toString()))
-
- // Update above assertions if field is added
- assertFieldCountEquals(5, KeepalivePacketData::class.java)
- assertFieldCountEquals(6, TcpKeepalivePacketData::class.java)
}
}
\ No newline at end of file
diff --git a/tests/common/java/android/net/UidRangeTest.java b/tests/common/java/android/net/UidRangeTest.java
index 1b1c954..d46fdc9 100644
--- a/tests/common/java/android/net/UidRangeTest.java
+++ b/tests/common/java/android/net/UidRangeTest.java
@@ -22,15 +22,20 @@
import static android.os.UserHandle.getUid;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Build;
import android.os.UserHandle;
+import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -38,8 +43,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Set;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
+@ConnectivityModuleTest
public class UidRangeTest {
/*
@@ -110,4 +118,61 @@
assertEquals(USER_SYSTEM + 1, uidRangeOfSecondaryUser.getStartUser());
assertEquals(USER_SYSTEM + 1, uidRangeOfSecondaryUser.getEndUser());
}
+
+ private static void assertSameUids(@NonNull final String msg, @Nullable final Set<UidRange> s1,
+ @Nullable final Set<UidRange> s2) {
+ assertTrue(msg + " : " + s1 + " unexpectedly different from " + s2,
+ UidRange.hasSameUids(s1, s2));
+ }
+
+ private static void assertDifferentUids(@NonNull final String msg,
+ @Nullable final Set<UidRange> s1, @Nullable final Set<UidRange> s2) {
+ assertFalse(msg + " : " + s1 + " unexpectedly equal to " + s2,
+ UidRange.hasSameUids(s1, s2));
+ }
+
+ // R doesn't have UidRange.hasSameUids, but since S has the module, it does have hasSameUids.
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testHasSameUids() {
+ final UidRange uids1 = new UidRange(1, 100);
+ final UidRange uids2 = new UidRange(3, 300);
+ final UidRange uids3 = new UidRange(1, 1000);
+ final UidRange uids4 = new UidRange(800, 1000);
+
+ assertSameUids("null <=> null", null, null);
+ final Set<UidRange> set1 = new ArraySet<>();
+ assertDifferentUids("empty <=> null", set1, null);
+ final Set<UidRange> set2 = new ArraySet<>();
+ set1.add(uids1);
+ assertDifferentUids("uids1 <=> null", set1, null);
+ assertDifferentUids("null <=> uids1", null, set1);
+ assertDifferentUids("uids1 <=> empty", set1, set2);
+ set2.add(uids1);
+ assertSameUids("uids1 <=> uids1", set1, set2);
+ set1.add(uids2);
+ assertDifferentUids("uids1,2 <=> uids1", set1, set2);
+ set1.add(uids3);
+ assertDifferentUids("uids1,2,3 <=> uids1", set1, set2);
+ set2.add(uids3);
+ assertDifferentUids("uids1,2,3 <=> uids1,3", set1, set2);
+ set2.add(uids2);
+ assertSameUids("uids1,2,3 <=> uids1,2,3", set1, set2);
+ set1.remove(uids2);
+ assertDifferentUids("uids1,3 <=> uids1,2,3", set1, set2);
+ set1.add(uids4);
+ assertDifferentUids("uids1,3,4 <=> uids1,2,3", set1, set2);
+ set2.add(uids4);
+ assertDifferentUids("uids1,3,4 <=> uids1,2,3,4", set1, set2);
+ assertDifferentUids("uids1,3,4 <=> null", set1, null);
+ set2.remove(uids2);
+ assertSameUids("uids1,3,4 <=> uids1,3,4", set1, set2);
+ set2.remove(uids1);
+ assertDifferentUids("uids1,3,4 <=> uids3,4", set1, set2);
+ set2.remove(uids3);
+ assertDifferentUids("uids1,3,4 <=> uids4", set1, set2);
+ set2.remove(uids4);
+ assertDifferentUids("uids1,3,4 <=> empty", set1, set2);
+ assertDifferentUids("null <=> empty", null, set2);
+ assertSameUids("empty <=> empty", set2, new ArraySet<>());
+ }
}
diff --git a/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt b/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt
index f23ba26..a041c4e 100644
--- a/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt
+++ b/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt
@@ -18,9 +18,10 @@
import android.os.Build
import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
@@ -32,6 +33,7 @@
@SmallTest
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@ConnectivityModuleTest
class UnderlyingNetworkInfoTest {
@Test
fun testParcelUnparcel() {
@@ -39,12 +41,12 @@
assertEquals(TEST_OWNER_UID, testInfo.getOwnerUid())
assertEquals(TEST_IFACE, testInfo.getInterface())
assertEquals(TEST_IFACE_LIST, testInfo.getUnderlyingInterfaces())
- assertParcelSane(testInfo, 3)
+ assertParcelingIsLossless(testInfo)
val emptyInfo = UnderlyingNetworkInfo(0, String(), listOf())
assertEquals(0, emptyInfo.getOwnerUid())
assertEquals(String(), emptyInfo.getInterface())
assertEquals(listOf(), emptyInfo.getUnderlyingInterfaces())
- assertParcelSane(emptyInfo, 3)
+ assertParcelingIsLossless(emptyInfo)
}
}
\ No newline at end of file
diff --git a/tests/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/common/java/android/net/apf/ApfCapabilitiesTest.java
index 88996d9..fa4adcb 100644
--- a/tests/common/java/android/net/apf/ApfCapabilitiesTest.java
+++ b/tests/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -16,7 +16,7 @@
package android.net.apf;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -62,7 +62,7 @@
assertEquals(456, caps.maximumApfProgramSize);
assertEquals(789, caps.apfPacketFormat);
- assertParcelSane(caps, 3);
+ assertParcelingIsLossless(caps);
}
@Test
diff --git a/tests/common/java/android/net/metrics/ApfProgramEventTest.kt b/tests/common/java/android/net/metrics/ApfProgramEventTest.kt
index 0b7b740..1c175da 100644
--- a/tests/common/java/android/net/metrics/ApfProgramEventTest.kt
+++ b/tests/common/java/android/net/metrics/ApfProgramEventTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -48,7 +48,7 @@
assertEquals(5, apfProgramEvent.programLength)
assertEquals(ApfProgramEvent.flagsFor(true, true), apfProgramEvent.flags)
- assertParcelSane(apfProgramEvent, 6)
+ assertParcelingIsLossless(apfProgramEvent)
}
@Test
diff --git a/tests/common/java/android/net/metrics/ApfStatsTest.kt b/tests/common/java/android/net/metrics/ApfStatsTest.kt
index 46a8c8e..610e674 100644
--- a/tests/common/java/android/net/metrics/ApfStatsTest.kt
+++ b/tests/common/java/android/net/metrics/ApfStatsTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,6 +52,6 @@
assertEquals(8, apfStats.programUpdatesAllowingMulticast)
assertEquals(9, apfStats.maxProgramSize)
- assertParcelSane(apfStats, 10)
+ assertParcelingIsLossless(apfStats)
}
}
diff --git a/tests/common/java/android/net/metrics/DhcpClientEventTest.kt b/tests/common/java/android/net/metrics/DhcpClientEventTest.kt
index 8d7a9c4..4c70e11 100644
--- a/tests/common/java/android/net/metrics/DhcpClientEventTest.kt
+++ b/tests/common/java/android/net/metrics/DhcpClientEventTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,6 +38,6 @@
assertEquals(FAKE_MESSAGE, dhcpClientEvent.msg)
assertEquals(Integer.MAX_VALUE, dhcpClientEvent.durationMs)
- assertParcelSane(dhcpClientEvent, 2)
+ assertParcelingIsLossless(dhcpClientEvent)
}
}
diff --git a/tests/common/java/android/net/metrics/IpManagerEventTest.kt b/tests/common/java/android/net/metrics/IpManagerEventTest.kt
index 64be508..bb21dca 100644
--- a/tests/common/java/android/net/metrics/IpManagerEventTest.kt
+++ b/tests/common/java/android/net/metrics/IpManagerEventTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,7 +33,7 @@
assertEquals(it, ipManagerEvent.eventType)
assertEquals(Long.MAX_VALUE, ipManagerEvent.durationMs)
- assertParcelSane(ipManagerEvent, 2)
+ assertParcelingIsLossless(ipManagerEvent)
}
}
}
diff --git a/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt b/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt
index 55b5e49..3d21b81 100644
--- a/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt
+++ b/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -32,7 +32,7 @@
val ipReachabilityEvent = IpReachabilityEvent(it)
assertEquals(it, ipReachabilityEvent.eventType)
- assertParcelSane(ipReachabilityEvent, 1)
+ assertParcelingIsLossless(ipReachabilityEvent)
}
}
}
diff --git a/tests/common/java/android/net/metrics/NetworkEventTest.kt b/tests/common/java/android/net/metrics/NetworkEventTest.kt
index 41430b0..17b5e2d 100644
--- a/tests/common/java/android/net/metrics/NetworkEventTest.kt
+++ b/tests/common/java/android/net/metrics/NetworkEventTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,7 +37,7 @@
assertEquals(it, networkEvent.eventType)
assertEquals(Long.MAX_VALUE, networkEvent.durationMs)
- assertParcelSane(networkEvent, 2)
+ assertParcelingIsLossless(networkEvent)
}
}
}
diff --git a/tests/common/java/android/net/metrics/RaEventTest.kt b/tests/common/java/android/net/metrics/RaEventTest.kt
index d9b7203..e9daa0f 100644
--- a/tests/common/java/android/net/metrics/RaEventTest.kt
+++ b/tests/common/java/android/net/metrics/RaEventTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -67,6 +67,6 @@
assertEquals(5, raEvent.rdnssLifetime)
assertEquals(6, raEvent.dnsslLifetime)
- assertParcelSane(raEvent, 6)
+ assertParcelingIsLossless(raEvent)
}
}
diff --git a/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt b/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt
index 51c0d41..7dfa7e1 100644
--- a/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt
+++ b/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
import java.lang.reflect.Modifier
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -51,7 +51,7 @@
assertTrue(validationProbeEvent.probeType hasType FIRST_VALIDATION)
assertEquals(ValidationProbeEvent.DNS_SUCCESS, validationProbeEvent.returnCode)
- assertParcelSane(validationProbeEvent, 3)
+ assertParcelingIsLossless(validationProbeEvent)
}
@Test
diff --git a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
index 7b22e45..c90b1aa 100644
--- a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
@@ -31,7 +31,6 @@
import android.os.Build
import androidx.test.filters.SmallTest
import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.assertFieldCountEquals
import com.android.testutils.assertNetworkStatsEquals
import com.android.testutils.assertParcelingIsLossless
import org.junit.Before
@@ -176,7 +175,6 @@
assertParcelingIsLossless(testStatsEmpty)
assertParcelingIsLossless(testStats1)
assertParcelingIsLossless(testStats2)
- assertFieldCountEquals(15, NetworkStats::class.java)
}
@Test
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index 4264345..8dfa455 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 31808
set noparent
-lorenzo@google.com
-satk@google.com
\ No newline at end of file
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index f72a458..b684068 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -25,6 +25,9 @@
"cts-tradefed",
"tradefed",
],
+ static_libs: [
+ "modules-utils-build-testing",
+ ],
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index 63572c3..12e7d33 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -18,12 +18,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test_helper_app {
- name: "CtsHostsideNetworkTestsApp",
- defaults: [
- "cts_support_defaults",
- "framework-connectivity-test-defaults",
- ],
+java_defaults {
+ name: "CtsHostsideNetworkTestsAppDefaults",
platform_apis: true,
static_libs: [
"CtsHostsideNetworkTestsAidl",
@@ -48,3 +44,28 @@
"sts",
],
}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkTestsApp",
+ defaults: [
+ "cts_support_defaults",
+ "framework-connectivity-test-defaults",
+ "CtsHostsideNetworkTestsAppDefaults",
+ ],
+ static_libs: [
+ "NetworkStackApiStableShims",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkTestsAppNext",
+ defaults: [
+ "cts_support_defaults",
+ "framework-connectivity-test-defaults",
+ "CtsHostsideNetworkTestsAppDefaults",
+ "ConnectivityNextEnableDefaults",
+ ],
+ static_libs: [
+ "NetworkStackApiCurrentShims",
+ ],
+}
diff --git a/tests/cts/hostside/app/AndroidManifest.xml b/tests/cts/hostside/app/AndroidManifest.xml
index e5bae5f..d56e5d4 100644
--- a/tests/cts/hostside/app/AndroidManifest.xml
+++ b/tests/cts/hostside/app/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
index 7d3d4fc..449454e 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
@@ -17,18 +17,27 @@
package com.android.cts.net.hostside;
import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.IpPrefix;
import android.net.Network;
+import android.net.NetworkUtils;
import android.net.ProxyInfo;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.networkstack.apishim.VpnServiceBuilderShimImpl;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.networkstack.apishim.common.VpnServiceBuilderShim;
import java.io.IOException;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
public class MyVpnService extends VpnService {
@@ -38,6 +47,9 @@
public static final String ACTION_ESTABLISHED = "com.android.cts.net.hostside.ESTABNLISHED";
public static final String EXTRA_ALWAYS_ON = "is-always-on";
public static final String EXTRA_LOCKDOWN_ENABLED = "is-lockdown-enabled";
+ public static final String CMD_CONNECT = "connect";
+ public static final String CMD_DISCONNECT = "disconnect";
+ public static final String CMD_UPDATE_UNDERLYING_NETWORKS = "update_underlying_networks";
private ParcelFileDescriptor mFd = null;
private PacketReflector mPacketReflector = null;
@@ -46,48 +58,80 @@
public int onStartCommand(Intent intent, int flags, int startId) {
String packageName = getPackageName();
String cmd = intent.getStringExtra(packageName + ".cmd");
- if ("disconnect".equals(cmd)) {
+ if (CMD_DISCONNECT.equals(cmd)) {
stop();
- } else if ("connect".equals(cmd)) {
+ } else if (CMD_CONNECT.equals(cmd)) {
start(packageName, intent);
+ } else if (CMD_UPDATE_UNDERLYING_NETWORKS.equals(cmd)) {
+ updateUnderlyingNetworks(packageName, intent);
}
return START_NOT_STICKY;
}
- private void start(String packageName, Intent intent) {
- Builder builder = new Builder();
+ private void updateUnderlyingNetworks(String packageName, Intent intent) {
+ final ArrayList<Network> underlyingNetworks =
+ intent.getParcelableArrayListExtra(packageName + ".underlyingNetworks");
+ setUnderlyingNetworks(
+ (underlyingNetworks != null) ? underlyingNetworks.toArray(new Network[0]) : null);
+ }
- String addresses = intent.getStringExtra(packageName + ".addresses");
- if (addresses != null) {
- String[] addressArray = addresses.split(",");
- for (int i = 0; i < addressArray.length; i++) {
- String[] prefixAndMask = addressArray[i].split("/");
- try {
- InetAddress address = InetAddress.getByName(prefixAndMask[0]);
- int prefixLength = Integer.parseInt(prefixAndMask[1]);
- builder.addAddress(address, prefixLength);
- } catch (UnknownHostException|NumberFormatException|
- ArrayIndexOutOfBoundsException e) {
- continue;
- }
- }
+ private String parseIpAndMaskListArgument(String packageName, Intent intent, String argName,
+ BiConsumer<InetAddress, Integer> consumer) {
+ final String addresses = intent.getStringExtra(packageName + "." + argName);
+
+ if (TextUtils.isEmpty(addresses)) {
+ return null;
}
- String routes = intent.getStringExtra(packageName + ".routes");
- if (routes != null) {
- String[] routeArray = routes.split(",");
- for (int i = 0; i < routeArray.length; i++) {
- String[] prefixAndMask = routeArray[i].split("/");
+ final String[] addressesArray = addresses.split(",");
+ for (String address : addressesArray) {
+ final Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address);
+ consumer.accept(ipAndMask.first, ipAndMask.second);
+ }
+
+ return addresses;
+ }
+
+ private String parseIpPrefixListArgument(String packageName, Intent intent, String argName,
+ Consumer<IpPrefix> consumer) {
+ return parseIpAndMaskListArgument(packageName, intent, argName,
+ (inetAddress, prefixLength) -> consumer.accept(
+ new IpPrefix(inetAddress, prefixLength)));
+ }
+
+ private void start(String packageName, Intent intent) {
+ Builder builder = new Builder();
+ VpnServiceBuilderShim vpnServiceBuilderShim = VpnServiceBuilderShimImpl.newInstance();
+
+ final String addresses = parseIpAndMaskListArgument(packageName, intent, "addresses",
+ builder::addAddress);
+
+ String addedRoutes;
+ if (SdkLevel.isAtLeastT() && intent.getBooleanExtra(packageName + ".addRoutesByIpPrefix",
+ false)) {
+ addedRoutes = parseIpPrefixListArgument(packageName, intent, "routes", (prefix) -> {
try {
- InetAddress address = InetAddress.getByName(prefixAndMask[0]);
- int prefixLength = Integer.parseInt(prefixAndMask[1]);
- builder.addRoute(address, prefixLength);
- } catch (UnknownHostException|NumberFormatException|
- ArrayIndexOutOfBoundsException e) {
- continue;
+ vpnServiceBuilderShim.addRoute(builder, prefix);
+ } catch (UnsupportedApiLevelException e) {
+ throw new RuntimeException(e);
}
- }
+ });
+ } else {
+ addedRoutes = parseIpAndMaskListArgument(packageName, intent, "routes",
+ builder::addRoute);
+ }
+
+ String excludedRoutes = null;
+ if (SdkLevel.isAtLeastT()) {
+ excludedRoutes = parseIpPrefixListArgument(packageName, intent, "excludedRoutes",
+ (prefix) -> {
+ try {
+ vpnServiceBuilderShim.excludeRoute(builder, prefix);
+ } catch (UnsupportedApiLevelException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
String allowed = intent.getStringExtra(packageName + ".allowedapplications");
@@ -140,7 +184,8 @@
Log.i(TAG, "Establishing VPN,"
+ " addresses=" + addresses
- + " routes=" + routes
+ + " addedRoutes=" + addedRoutes
+ + " excludedRoutes=" + excludedRoutes
+ " allowedApplications=" + allowed
+ " disallowedApplications=" + disallowed);
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 3abc4fb..5778b0d 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
@@ -17,6 +17,8 @@
package com.android.cts.net.hostside;
import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.os.Process.INVALID_UID;
@@ -33,12 +35,15 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.testutils.Cleanup.testAndCleanup;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.annotation.Nullable;
import android.app.Activity;
@@ -65,6 +70,7 @@
import android.net.VpnManager;
import android.net.VpnService;
import android.net.VpnTransportInfo;
+import android.net.cts.util.CtsNetUtils;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Looper;
@@ -80,6 +86,7 @@
import android.system.Os;
import android.system.OsConstants;
import android.system.StructPollfd;
+import android.telephony.TelephonyManager;
import android.test.MoreAsserts;
import android.text.TextUtils;
import android.util.Log;
@@ -88,10 +95,14 @@
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.RecorderCallback;
import com.android.testutils.TestableNetworkCallback;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -151,6 +162,7 @@
private static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
private static final String PRIVATE_DNS_SPECIFIER_SETTING = "private_dns_specifier";
+ private static final int NETWORK_CALLBACK_TIMEOUT_MS = 30_000;
public static String TAG = "VpnTest";
public static int TIMEOUT_MS = 3 * 1000;
@@ -163,6 +175,9 @@
private ConnectivityManager mCM;
private WifiManager mWifiManager;
private RemoteSocketFactoryClient mRemoteSocketFactoryClient;
+ private CtsNetUtils mCtsNetUtils;
+ private PackageManager mPackageManager;
+ private TelephonyManager mTelephonyManager;
Network mNetwork;
NetworkCallback mCallback;
@@ -172,6 +187,9 @@
private String mOldPrivateDnsMode;
private String mOldPrivateDnsSpecifier;
+ @Rule
+ public final DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
+
private boolean supportedHardware() {
final PackageManager pm = getInstrumentation().getContext().getPackageManager();
return !pm.hasSystemFeature("android.hardware.type.watch");
@@ -201,6 +219,10 @@
mRemoteSocketFactoryClient = new RemoteSocketFactoryClient(mActivity);
mRemoteSocketFactoryClient.bind();
mDevice.waitForIdle();
+ mCtsNetUtils = new CtsNetUtils(getInstrumentation().getContext());
+ mPackageManager = getInstrumentation().getContext().getPackageManager();
+ mTelephonyManager =
+ getInstrumentation().getContext().getSystemService(TelephonyManager.class);
}
@After
@@ -210,6 +232,7 @@
if (mCallback != null) {
mCM.unregisterNetworkCallback(mCallback);
}
+ mCtsNetUtils.tearDown();
Log.i(TAG, "Stopping VPN");
stopVpn();
mActivity.finish();
@@ -266,11 +289,63 @@
}
}
+ private void updateUnderlyingNetworks(@Nullable ArrayList<Network> underlyingNetworks)
+ throws Exception {
+ final Intent intent = new Intent(mActivity, MyVpnService.class)
+ .putExtra(mPackageName + ".cmd", MyVpnService.CMD_UPDATE_UNDERLYING_NETWORKS)
+ .putParcelableArrayListExtra(
+ mPackageName + ".underlyingNetworks", underlyingNetworks);
+ mActivity.startService(intent);
+ }
+
+ private void establishVpn(String[] addresses, String[] routes, String[] excludedRoutes,
+ String allowedApplications, String disallowedApplications,
+ @Nullable ProxyInfo proxyInfo, @Nullable ArrayList<Network> underlyingNetworks,
+ boolean isAlwaysMetered, boolean addRoutesByIpPrefix)
+ throws Exception {
+ final Intent intent = new Intent(mActivity, MyVpnService.class)
+ .putExtra(mPackageName + ".cmd", MyVpnService.CMD_CONNECT)
+ .putExtra(mPackageName + ".addresses", TextUtils.join(",", addresses))
+ .putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
+ .putExtra(mPackageName + ".excludedRoutes", TextUtils.join(",", excludedRoutes))
+ .putExtra(mPackageName + ".allowedapplications", allowedApplications)
+ .putExtra(mPackageName + ".disallowedapplications", disallowedApplications)
+ .putExtra(mPackageName + ".httpProxy", proxyInfo)
+ .putParcelableArrayListExtra(
+ mPackageName + ".underlyingNetworks", underlyingNetworks)
+ .putExtra(mPackageName + ".isAlwaysMetered", isAlwaysMetered)
+ .putExtra(mPackageName + ".addRoutesByIpPrefix", addRoutesByIpPrefix);
+ mActivity.startService(intent);
+ }
+
// TODO: Consider replacing arguments with a Builder.
private void startVpn(
- String[] addresses, String[] routes, String allowedApplications,
- String disallowedApplications, @Nullable ProxyInfo proxyInfo,
- @Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered) throws Exception {
+ String[] addresses, String[] routes, String allowedApplications,
+ String disallowedApplications, @Nullable ProxyInfo proxyInfo,
+ @Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered)
+ throws Exception {
+ startVpn(addresses, routes, new String[0] /* excludedRoutes */, allowedApplications,
+ disallowedApplications, proxyInfo, underlyingNetworks, isAlwaysMetered);
+ }
+
+ private void startVpn(
+ String[] addresses, String[] routes, String[] excludedRoutes,
+ String allowedApplications, String disallowedApplications,
+ @Nullable ProxyInfo proxyInfo,
+ @Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered)
+ throws Exception {
+ startVpn(addresses, routes, new String[0] /* excludedRoutes */, allowedApplications,
+ disallowedApplications, proxyInfo, underlyingNetworks, isAlwaysMetered,
+ false /* addRoutesByIpPrefix */);
+ }
+
+ private void startVpn(
+ String[] addresses, String[] routes, String[] excludedRoutes,
+ String allowedApplications, String disallowedApplications,
+ @Nullable ProxyInfo proxyInfo,
+ @Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered,
+ boolean addRoutesByIpPrefix)
+ throws Exception {
prepareVpn();
// Register a callback so we will be notified when our VPN comes up.
@@ -291,18 +366,8 @@
mCM.registerNetworkCallback(request, mCallback); // Unregistered in tearDown.
// Start the service and wait up for TIMEOUT_MS ms for the VPN to come up.
- Intent intent = new Intent(mActivity, MyVpnService.class)
- .putExtra(mPackageName + ".cmd", "connect")
- .putExtra(mPackageName + ".addresses", TextUtils.join(",", addresses))
- .putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
- .putExtra(mPackageName + ".allowedapplications", allowedApplications)
- .putExtra(mPackageName + ".disallowedapplications", disallowedApplications)
- .putExtra(mPackageName + ".httpProxy", proxyInfo)
- .putParcelableArrayListExtra(
- mPackageName + ".underlyingNetworks", underlyingNetworks)
- .putExtra(mPackageName + ".isAlwaysMetered", isAlwaysMetered);
-
- mActivity.startService(intent);
+ establishVpn(addresses, routes, excludedRoutes, allowedApplications, disallowedApplications,
+ proxyInfo, underlyingNetworks, isAlwaysMetered, addRoutesByIpPrefix);
synchronized (mLock) {
if (mNetwork == null) {
Log.i(TAG, "bf mLock");
@@ -344,7 +409,7 @@
// and stopping a bound service has no effect. Instead, "start" the service again with an
// Intent that tells it to disconnect.
Intent intent = new Intent(mActivity, MyVpnService.class)
- .putExtra(mPackageName + ".cmd", "disconnect");
+ .putExtra(mPackageName + ".cmd", MyVpnService.CMD_DISCONNECT);
mActivity.startService(intent);
synchronized (mLockShutdown) {
try {
@@ -522,6 +587,12 @@
}
private void checkUdpEcho(String to, String expectedFrom) throws IOException {
+ checkUdpEcho(to, expectedFrom, expectedFrom != null);
+ }
+
+ private void checkUdpEcho(String to, String expectedFrom,
+ boolean expectConnectionOwnerIsVisible)
+ throws IOException {
DatagramSocket s;
InetAddress address = InetAddress.getByName(to);
if (address instanceof Inet6Address) { // http://b/18094870
@@ -545,7 +616,7 @@
try {
if (expectedFrom != null) {
s.send(p);
- checkConnectionOwnerUidUdp(s, true);
+ checkConnectionOwnerUidUdp(s, expectConnectionOwnerIsVisible);
s.receive(p);
MoreAsserts.assertEquals(data, p.getData());
} else {
@@ -554,7 +625,7 @@
s.receive(p);
fail("Received unexpected reply");
} catch (IOException expected) {
- checkConnectionOwnerUidUdp(s, false);
+ checkConnectionOwnerUidUdp(s, expectConnectionOwnerIsVisible);
}
}
} finally {
@@ -562,19 +633,38 @@
}
}
+ private void checkTrafficOnVpn(String destination) throws Exception {
+ final InetAddress address = InetAddress.getByName(destination);
+
+ if (address instanceof Inet6Address) {
+ checkUdpEcho(destination, "2001:db8:1:2::ffe");
+ checkTcpReflection(destination, "2001:db8:1:2::ffe");
+ checkPing(destination);
+ } else {
+ checkUdpEcho(destination, "192.0.2.2");
+ checkTcpReflection(destination, "192.0.2.2");
+ }
+
+ }
+
+ private void checkNoTrafficOnVpn(String destination) throws IOException {
+ checkUdpEcho(destination, null);
+ checkTcpReflection(destination, null);
+ }
+
private void checkTrafficOnVpn() throws Exception {
- checkUdpEcho("192.0.2.251", "192.0.2.2");
- checkUdpEcho("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
- checkPing("2001:db8:dead:beef::f00");
- checkTcpReflection("192.0.2.252", "192.0.2.2");
- checkTcpReflection("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
+ checkTrafficOnVpn("192.0.2.251");
+ checkTrafficOnVpn("2001:db8:dead:beef::f00");
}
private void checkNoTrafficOnVpn() throws Exception {
- checkUdpEcho("192.0.2.251", null);
- checkUdpEcho("2001:db8:dead:beef::f00", null);
- checkTcpReflection("192.0.2.252", null);
- checkTcpReflection("2001:db8:dead:beef::f00", null);
+ checkNoTrafficOnVpn("192.0.2.251");
+ checkNoTrafficOnVpn("2001:db8:dead:beef::f00");
+ }
+
+ private void checkTrafficBypassesVpn(String destination) throws Exception {
+ checkUdpEcho(destination, null, true /* expectVpnOwnedConnection */);
+ checkTcpReflection(destination, null);
}
private FileDescriptor openSocketFd(String host, int port, int timeoutMs) throws Exception {
@@ -724,9 +814,86 @@
setAndVerifyPrivateDns(initialMode);
}
+ private NetworkRequest makeVpnNetworkRequest() {
+ return new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .build();
+ }
+
+ private void expectUnderlyingNetworks(TestableNetworkCallback callback,
+ @Nullable List<Network> expectUnderlyingNetworks) {
+ callback.eventuallyExpect(RecorderCallback.CallbackEntry.NETWORK_CAPS_UPDATED,
+ NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> (Objects.equals(expectUnderlyingNetworks,
+ ((RecorderCallback.CallbackEntry.CapabilitiesChanged) entry)
+ .getCaps().getUnderlyingNetworks())));
+ }
+
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+ public void testChangeUnderlyingNetworks() throws Exception {
+ assumeTrue(supportedHardware());
+ assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
+ assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
+ final TestableNetworkCallback callback = new TestableNetworkCallback();
+ final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
+ testAndCleanup(() -> {
+ // Ensure both of wifi and mobile data are connected.
+ final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+ assertTrue("Wifi is not connected", (wifiNetwork != null));
+ final Network cellNetwork = mCtsNetUtils.connectToCell();
+ assertTrue("Mobile data is not connected", (cellNetwork != null));
+ // Store current default network.
+ final Network defaultNetwork = mCM.getActiveNetwork();
+ // Start VPN and set empty array as its underlying networks.
+ startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"} /* addresses */,
+ new String[] {"0.0.0.0/0", "::/0"} /* routes */,
+ "" /* allowedApplications */, "" /* disallowedApplications */,
+ null /* proxyInfo */, new ArrayList<>() /* underlyingNetworks */,
+ false /* isAlwaysMetered */);
+ // Acquire the NETWORK_SETTINGS permission for getting the underlying networks.
+ runWithShellPermissionIdentity(() -> {
+ mCM.registerNetworkCallback(makeVpnNetworkRequest(), callback);
+ // Check that this VPN doesn't have any underlying networks.
+ expectUnderlyingNetworks(callback, new ArrayList<Network>());
+
+ // Update the underlying networks to null and the underlying networks should follow
+ // the system default network.
+ updateUnderlyingNetworks(null);
+ expectUnderlyingNetworks(callback, List.of(defaultNetwork));
+
+ // Update the underlying networks to mobile data.
+ updateUnderlyingNetworks(new ArrayList<>(List.of(cellNetwork)));
+ // Check the underlying networks of NetworkCapabilities which comes from
+ // onCapabilitiesChanged is mobile data.
+ expectUnderlyingNetworks(callback, List.of(cellNetwork));
+
+ // Update the underlying networks to wifi.
+ updateUnderlyingNetworks(new ArrayList<>(List.of(wifiNetwork)));
+ // Check the underlying networks of NetworkCapabilities which comes from
+ // onCapabilitiesChanged is wifi.
+ expectUnderlyingNetworks(callback, List.of(wifiNetwork));
+
+ // Update the underlying networks to wifi and mobile data.
+ updateUnderlyingNetworks(new ArrayList<>(List.of(wifiNetwork, cellNetwork)));
+ // Check the underlying networks of NetworkCapabilities which comes from
+ // onCapabilitiesChanged is wifi and mobile data.
+ expectUnderlyingNetworks(callback, List.of(wifiNetwork, cellNetwork));
+ }, NETWORK_SETTINGS);
+ }, () -> {
+ if (isWifiEnabled) {
+ mCtsNetUtils.ensureWifiConnected();
+ } else {
+ mCtsNetUtils.ensureWifiDisconnected(null);
+ }
+ }, () -> {
+ mCM.unregisterNetworkCallback(callback);
+ });
+ }
+
@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)) {
@@ -819,7 +986,7 @@
@Test
public void testAppAllowed() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
@@ -840,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);
@@ -858,9 +1025,9 @@
}
Log.i(TAG, "Append shell app to disallowedApps: " + disallowedApps);
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
- new String[] {"192.0.2.0/24", "2001:db8::/32"},
- "", disallowedApps, null, null /* underlyingNetworks */,
- false /* isAlwaysMetered */);
+ new String[] {"192.0.2.0/24", "2001:db8::/32"},
+ "", disallowedApps, null, null /* underlyingNetworks */,
+ false /* isAlwaysMetered */);
assertSocketStillOpen(localFd, TEST_HOST);
assertSocketStillOpen(remoteFd, TEST_HOST);
@@ -873,8 +1040,76 @@
}
@Test
+ public void testExcludedRoutes() throws Exception {
+ assumeTrue(supportedHardware());
+ assumeTrue(SdkLevel.isAtLeastT());
+
+ // Shell app must not be put in here or it would kill the ADB-over-network use case
+ String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
+ startVpn(new String[]{"192.0.2.2/32", "2001:db8:1:2::ffe/128"} /* addresses */,
+ new String[]{"0.0.0.0/0", "::/0"} /* routes */,
+ new String[]{"192.0.2.0/24", "2001:db8::/32"} /* excludedRoutes */,
+ allowedApps, "" /* disallowedApplications */, null /* proxyInfo */,
+ null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+ // Excluded routes should bypass VPN.
+ checkTrafficBypassesVpn("192.0.2.1");
+ checkTrafficBypassesVpn("2001:db8:dead:beef::f00");
+ // Other routes should go through VPN, since default routes are included.
+ checkTrafficOnVpn("198.51.100.1");
+ checkTrafficOnVpn("2002:db8::1");
+ }
+
+ @Test
+ public void testIncludedRoutes() throws Exception {
+ assumeTrue(supportedHardware());
+
+ // Shell app must not be put in here or it would kill the ADB-over-network use case
+ String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
+ startVpn(new String[]{"192.0.2.2/32", "2001:db8:1:2::ffe/128"} /* addresses */,
+ new String[]{"192.0.2.0/24", "2001:db8::/32"} /* routes */,
+ allowedApps, "" /* disallowedApplications */, null /* proxyInfo */,
+ null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+ // Included routes should go through VPN.
+ checkTrafficOnVpn("192.0.2.1");
+ checkTrafficOnVpn("2001:db8:dead:beef::f00");
+ // Other routes should bypass VPN, since default routes are not included.
+ checkTrafficBypassesVpn("198.51.100.1");
+ checkTrafficBypassesVpn("2002:db8::1");
+ }
+
+ @Test
+ public void testInterleavedRoutes() throws Exception {
+ assumeTrue(supportedHardware());
+ assumeTrue(SdkLevel.isAtLeastT());
+
+ // Shell app must not be put in here or it would kill the ADB-over-network use case
+ String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
+ startVpn(new String[]{"192.0.2.2/32", "2001:db8:1:2::ffe/128"} /* addresses */,
+ new String[]{"0.0.0.0/0", "192.0.2.0/32", "::/0", "2001:db8::/128"} /* routes */,
+ new String[]{"192.0.2.0/24", "2001:db8::/32"} /* excludedRoutes */,
+ allowedApps, "" /* disallowedApplications */, null /* proxyInfo */,
+ null /* underlyingNetworks */, false /* isAlwaysMetered */,
+ true /* addRoutesByIpPrefix */);
+
+ // Excluded routes should bypass VPN.
+ checkTrafficBypassesVpn("192.0.2.1");
+ checkTrafficBypassesVpn("2001:db8:dead:beef::f00");
+
+ // Included routes inside excluded routes should go through VPN, since the longest common
+ // prefix precedes.
+ checkTrafficOnVpn("192.0.2.0");
+ checkTrafficOnVpn("2001:db8::");
+
+ // Other routes should go through VPN, since default routes are included.
+ checkTrafficOnVpn("198.51.100.1");
+ checkTrafficOnVpn("2002:db8::1");
+ }
+
+ @Test
public void testGetConnectionOwnerUidSecurity() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
DatagramSocket s;
InetAddress address = InetAddress.getByName("localhost");
@@ -896,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();
@@ -936,7 +1171,7 @@
@Test
public void testSetProxyDisallowedApps() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
ProxyInfo initialProxy = mCM.getDefaultProxy();
String disallowedApps = mPackageName;
@@ -962,7 +1197,7 @@
@Test
public void testNoProxy() throws Exception {
- if (!supportedHardware()) return;
+ assumeTrue(supportedHardware());
ProxyInfo initialProxy = mCM.getDefaultProxy();
BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
proxyBroadcastReceiver.register();
@@ -997,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();
@@ -1239,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();
@@ -1291,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/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index 89c79d3..cc07fd1 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -20,6 +20,7 @@
import com.android.ddmlib.Log;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.result.CollectingTestListener;
@@ -42,6 +43,7 @@
protected static final String TAG = "HostsideNetworkTests";
protected static final String TEST_PKG = "com.android.cts.net.hostside";
protected static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
+ protected static final String TEST_APK_NEXT = "CtsHostsideNetworkTestsAppNext.apk";
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
protected static final String TEST_APP2_APK = "CtsHostsideNetworkTestsApp2.apk";
@@ -65,8 +67,12 @@
assertNotNull(mAbi);
assertNotNull(mCtsBuild);
+ DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(getDevice());
+ String testApk = deviceSdkLevel.isDeviceAtLeastT() ? TEST_APK_NEXT
+ : TEST_APK;
+
uninstallPackage(TEST_PKG, false);
- installPackage(TEST_APK);
+ installPackage(testApk);
}
@Override
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 49b5f9d..3821f87 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -33,6 +33,10 @@
uninstallPackage(TEST_APP2_PKG, true);
}
+ public void testChangeUnderlyingNetworks() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testChangeUnderlyingNetworks");
+ }
+
public void testDefault() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
}
@@ -100,4 +104,16 @@
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest",
"testDownloadWithDownloadManagerDisallowed");
}
+
+ public void testExcludedRoutes() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testExcludedRoutes");
+ }
+
+ public void testIncludedRoutes() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testIncludedRoutes");
+ }
+
+ public void testInterleavedRoutes() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testInterleavedRoutes");
+ }
}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 81c30b1..f66231d 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -49,6 +49,7 @@
"FrameworksNetCommonTests",
"core-tests-support",
"cts-net-utils",
+ "CtsNetTestsNonUpdatableLib",
"ctstestrunner-axt",
"junit",
"junit-params",
diff --git a/tests/cts/net/OWNERS b/tests/cts/net/OWNERS
index 432bd9b..df5569e 100644
--- a/tests/cts/net/OWNERS
+++ b/tests/cts/net/OWNERS
@@ -1,3 +1,5 @@
# Bug component: 31808
# Inherits parent owners
per-file src/android/net/cts/NetworkWatchlistTest.java=alanstokes@google.com
+
+# Bug component: 685852 = per-file *IpSec*
\ No newline at end of file
diff --git a/tests/cts/net/native/Android.bp b/tests/cts/net/native/Android.bp
index 1d1c18e..153ff51 100644
--- a/tests/cts/net/native/Android.bp
+++ b/tests/cts/net/native/Android.bp
@@ -43,6 +43,7 @@
static_libs: [
"libbpf_android",
"libgtest",
+ "libmodules-utils-build",
],
// Tag this module as a cts test artifact
diff --git a/tests/cts/net/native/src/BpfCompatTest.cpp b/tests/cts/net/native/src/BpfCompatTest.cpp
index 874bad4..97ecb9e 100644
--- a/tests/cts/net/native/src/BpfCompatTest.cpp
+++ b/tests/cts/net/native/src/BpfCompatTest.cpp
@@ -21,6 +21,8 @@
#include <gtest/gtest.h>
+#include "android-modules-utils/sdk_level.h"
+
#include "libbpf_android.h"
using namespace android::bpf;
@@ -33,11 +35,17 @@
EXPECT_EQ(28, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
}
-TEST(BpfTest, bpfStructSizeTest) {
+TEST(BpfTest, bpfStructSizeTestPreT) {
+ if (android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() << "T+ device.";
doBpfStructSizeTest("/system/etc/bpf/netd.o");
doBpfStructSizeTest("/system/etc/bpf/clatd.o");
}
+TEST(BpfTest, bpfStructSizeTest) {
+ doBpfStructSizeTest("/system/etc/bpf/gpu_mem.o");
+ doBpfStructSizeTest("/system/etc/bpf/time_in_state.o");
+}
+
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 721ad82..232114e 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -40,6 +40,7 @@
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.testutils.Cleanup.testAndCleanup;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -84,7 +85,6 @@
import com.android.net.module.util.ArrayTrackRecord;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.SkipPresubmit;
import org.junit.After;
import org.junit.Before;
@@ -206,7 +206,6 @@
cb.assertNoCallback();
}
- @SkipPresubmit(reason = "Flaky: b/159718782; add to presubmit after fixing")
@Test
public void testRegisterCallbackWithCarrierPrivileges() throws Exception {
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
@@ -224,16 +223,16 @@
final TestNetworkCallback testNetworkCallback = new TestNetworkCallback();
- try {
+ testAndCleanup(() -> {
doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
subId, carrierConfigReceiver, testNetworkCallback);
- } finally {
+ }, () -> {
runWithShellPermissionIdentity(
() -> mCarrierConfigManager.overrideConfig(subId, null),
android.Manifest.permission.MODIFY_PHONE_STATE);
mConnectivityManager.unregisterNetworkCallback(testNetworkCallback);
mContext.unregisterReceiver(carrierConfigReceiver);
- }
+ });
}
private String getCertHashForThisPackage() throws Exception {
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 579be15..53e4ab7 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -168,6 +168,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRuleKt;
+import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
@@ -2380,7 +2381,7 @@
final String ssid = unquoteSSID(wifiNetworkCapabilities.getSsid());
final boolean oldMeteredValue = wifiNetworkCapabilities.isMetered();
- try {
+ testAndCleanup(() -> {
// This network will be used for unmetered. Wait for it to be validated because
// OEM_NETWORK_PREFERENCE_TEST only prefers NOT_METERED&VALIDATED to a network with
// TRANSPORT_TEST, like OEM_NETWORK_PREFERENCE_OEM_PAID.
@@ -2405,18 +2406,18 @@
// callback in any case therefore confirm its receipt before continuing to assure the
// system is in the expected state.
waitForAvailable(systemDefaultCallback, TRANSPORT_WIFI);
- } finally {
+ }, /* cleanup */ () -> {
// Validate that removing the test network will fallback to the default network.
runWithShellPermissionIdentity(tnt::teardown);
defaultCallback.expectCallback(CallbackEntry.LOST, tnt.getNetwork(),
NETWORK_CALLBACK_TIMEOUT_MS);
waitForAvailable(defaultCallback);
-
- setWifiMeteredStatusAndWait(ssid, oldMeteredValue, false /* waitForValidation */);
-
- // Cleanup any prior test state from setOemNetworkPreference
- clearOemNetworkPreference();
- }
+ }, /* cleanup */ () -> {
+ setWifiMeteredStatusAndWait(ssid, oldMeteredValue, false /* waitForValidation */);
+ }, /* cleanup */ () -> {
+ // Cleanup any prior test state from setOemNetworkPreference
+ clearOemNetworkPreference();
+ });
}
/**
@@ -2437,29 +2438,30 @@
final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
- try {
+ testAndCleanup(() -> {
setOemNetworkPreferenceForMyPackage(
OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY);
registerTestOemNetworkPreferenceCallbacks(defaultCallback, systemDefaultCallback);
waitForAvailable(defaultCallback, tnt.getNetwork());
waitForAvailable(systemDefaultCallback, wifiNetwork);
- } finally {
- runWithShellPermissionIdentity(tnt::teardown);
- defaultCallback.expectCallback(CallbackEntry.LOST, tnt.getNetwork(),
- NETWORK_CALLBACK_TIMEOUT_MS);
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(tnt::teardown);
+ defaultCallback.expectCallback(CallbackEntry.LOST, tnt.getNetwork(),
+ NETWORK_CALLBACK_TIMEOUT_MS);
- // This network preference should only ever use the test network therefore available
- // should not trigger when the test network goes down (e.g. switch to cellular).
- defaultCallback.assertNoCallback();
- // The system default should still be connected to Wi-fi
- assertEquals(wifiNetwork, systemDefaultCallback.getLastAvailableNetwork());
+ // This network preference should only ever use the test network therefore available
+ // should not trigger when the test network goes down (e.g. switch to cellular).
+ defaultCallback.assertNoCallback();
+ // The system default should still be connected to Wi-fi
+ assertEquals(wifiNetwork, systemDefaultCallback.getLastAvailableNetwork());
+ }, /* cleanup */ () -> {
+ // Cleanup any prior test state from setOemNetworkPreference
+ clearOemNetworkPreference();
- // Cleanup any prior test state from setOemNetworkPreference
- clearOemNetworkPreference();
-
- // The default (non-test) network should be available as the network pref was cleared.
- waitForAvailable(defaultCallback);
- }
+ // The default (non-test) network should be available as the network pref was
+ // cleared.
+ waitForAvailable(defaultCallback);
+ });
}
private void registerTestOemNetworkPreferenceCallbacks(
@@ -3020,6 +3022,13 @@
}
}
+ @Test
+ public void testDump() throws Exception {
+ final String dumpOutput = DumpTestUtils.dumpServiceWithShellPermission(
+ Context.CONNECTIVITY_SERVICE, "--short");
+ assertTrue(dumpOutput, dumpOutput.contains("Active default network"));
+ }
+
private void unregisterRegisteredCallbacks() {
for (NetworkCallback callback: mRegisteredCallbacks) {
mCm.unregisterNetworkCallback(callback);
diff --git a/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt b/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt
new file mode 100644
index 0000000..555dd87
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts
+
+import android.net.DhcpOption
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.runner.RunWith
+import org.junit.Test
+
+@SmallTest
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+@RunWith(DevSdkIgnoreRunner::class)
+class DhcpOptionTest {
+ private val DHCP_OPTION_TYPE: Byte = 2
+ private val DHCP_OPTION_VALUE = byteArrayOf(0, 1, 2, 4, 8, 16)
+
+ @Test
+ fun testConstructor() {
+ val dhcpOption = DhcpOption(DHCP_OPTION_TYPE, DHCP_OPTION_VALUE)
+ assertEquals(DHCP_OPTION_TYPE, dhcpOption.type)
+ assertArrayEquals(DHCP_OPTION_VALUE, dhcpOption.value)
+ }
+
+ @Test
+ fun testConstructorWithNullValue() {
+ val dhcpOption = DhcpOption(DHCP_OPTION_TYPE, null)
+ assertEquals(DHCP_OPTION_TYPE, dhcpOption.type)
+ assertNull(dhcpOption.value)
+ }
+}
\ No newline at end of file
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 22168b3..c6fc38f 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -25,15 +25,21 @@
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static android.system.OsConstants.ETIMEDOUT;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
import android.net.DnsResolver;
-import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
@@ -43,14 +49,23 @@
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
import android.system.ErrnoException;
-import android.test.AndroidTestCase;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.net.module.util.DnsPacket;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.SkipPresubmit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -61,7 +76,11 @@
import java.util.concurrent.TimeUnit;
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
-public class DnsResolverTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class DnsResolverTest {
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
private static final String TAG = "DnsResolverTest";
private static final char[] HEX_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
@@ -91,6 +110,7 @@
static final int QUERY_TIMES = 10;
static final int NXDOMAIN = 3;
+ private Context mContext;
private ContentResolver mCR;
private ConnectivityManager mCM;
private PackageManager mPackageManager;
@@ -99,30 +119,27 @@
private Executor mExecutorInline;
private DnsResolver mDns;
- private String mOldMode;
- private String mOldDnsSpecifier;
private TestNetworkCallback mWifiRequestCallback = null;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mCM = mContext.getSystemService(ConnectivityManager.class);
mDns = DnsResolver.getInstance();
mExecutor = new Handler(Looper.getMainLooper())::post;
mExecutorInline = (Runnable r) -> r.run();
- mCR = getContext().getContentResolver();
- mCtsNetUtils = new CtsNetUtils(getContext());
+ mCR = mContext.getContentResolver();
+ mCtsNetUtils = new CtsNetUtils(mContext);
mCtsNetUtils.storePrivateDnsSetting();
mPackageManager = mContext.getPackageManager();
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mCtsNetUtils.restorePrivateDnsSetting();
if (mWifiRequestCallback != null) {
mCM.unregisterNetworkCallback(mWifiRequestCallback);
}
- super.tearDown();
}
private static String byteArrayToHexString(byte[] bytes) {
@@ -298,42 +315,52 @@
}
}
+ @Test
public void testRawQuery() throws Exception {
doTestRawQuery(mExecutor);
}
+ @Test
public void testRawQueryInline() throws Exception {
doTestRawQuery(mExecutorInline);
}
+ @Test
public void testRawQueryBlob() throws Exception {
doTestRawQueryBlob(mExecutor);
}
+ @Test
public void testRawQueryBlobInline() throws Exception {
doTestRawQueryBlob(mExecutorInline);
}
+ @Test
public void testRawQueryRoot() throws Exception {
doTestRawQueryRoot(mExecutor);
}
+ @Test
public void testRawQueryRootInline() throws Exception {
doTestRawQueryRoot(mExecutorInline);
}
+ @Test
public void testRawQueryNXDomain() throws Exception {
doTestRawQueryNXDomain(mExecutor);
}
+ @Test
public void testRawQueryNXDomainInline() throws Exception {
doTestRawQueryNXDomain(mExecutorInline);
}
+ @Test
public void testRawQueryNXDomainWithPrivateDns() throws Exception {
doTestRawQueryNXDomainWithPrivateDns(mExecutor);
}
+ @Test
public void testRawQueryNXDomainInlineWithPrivateDns() throws Exception {
doTestRawQueryNXDomainWithPrivateDns(mExecutorInline);
}
@@ -436,6 +463,7 @@
}
}
+ @Test
public void testRawQueryCancel() throws InterruptedException {
final String msg = "Test cancel RawQuery " + TEST_DOMAIN;
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
@@ -465,6 +493,7 @@
}
}
+ @Test
public void testRawQueryBlobCancel() throws InterruptedException {
final String msg = "Test cancel RawQuery blob " + byteArrayToHexString(TEST_BLOB);
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
@@ -493,6 +522,7 @@
}
}
+ @Test
public void testCancelBeforeQuery() throws InterruptedException {
final String msg = "Test cancelled RawQuery " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
@@ -578,34 +608,42 @@
}
}
+ @Test
public void testQueryForInetAddress() throws Exception {
doTestQueryForInetAddress(mExecutor);
}
+ @Test
public void testQueryForInetAddressInline() throws Exception {
doTestQueryForInetAddress(mExecutorInline);
}
+ @Test
public void testQueryForInetAddressIpv4() throws Exception {
doTestQueryForInetAddressIpv4(mExecutor);
}
+ @Test
public void testQueryForInetAddressIpv4Inline() throws Exception {
doTestQueryForInetAddressIpv4(mExecutorInline);
}
+ @Test
public void testQueryForInetAddressIpv6() throws Exception {
doTestQueryForInetAddressIpv6(mExecutor);
}
+ @Test
public void testQueryForInetAddressIpv6Inline() throws Exception {
doTestQueryForInetAddressIpv6(mExecutorInline);
}
+ @Test
public void testContinuousQueries() throws Exception {
doTestContinuousQueries(mExecutor);
}
+ @Test
@SkipPresubmit(reason = "Flaky: b/159762682; add to presubmit after fixing")
public void testContinuousQueriesInline() throws Exception {
doTestContinuousQueries(mExecutorInline);
@@ -625,6 +663,7 @@
}
}
+ @Test
public void testQueryCancelForInetAddress() throws InterruptedException {
final String msg = "Test cancel query for InetAddress " + TEST_DOMAIN;
// Start a DNS query and the cancel it immediately. Use VerifyCancelInetAddressCallback to
@@ -686,6 +725,7 @@
}
}
+ @Test
public void testPrivateDnsBypass() throws InterruptedException {
final Network[] testNetworks = getTestableNetworks();
@@ -773,4 +813,19 @@
}
}
}
+
+ /** Verifies that DnsResolver.DnsException can be subclassed and its constructor re-used. */
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+ public void testDnsExceptionConstructor() throws InterruptedException {
+ class TestDnsException extends DnsResolver.DnsException {
+ TestDnsException(int code, @Nullable Throwable cause) {
+ super(code, cause);
+ }
+ }
+ try {
+ throw new TestDnsException(DnsResolver.ERROR_SYSTEM, null);
+ } catch (DnsResolver.DnsException e) {
+ assertEquals(DnsResolver.ERROR_SYSTEM, e.code);
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
index 56ab2a7..385bf9e 100644
--- a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
+++ b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
@@ -16,7 +16,7 @@
package android.net.cts;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -118,6 +118,6 @@
@Test
public void testParcel() {
final IpConfiguration config = new IpConfiguration();
- assertParcelSane(config, 4);
+ assertParcelingIsLossless(config);
}
}
diff --git a/tests/cts/net/src/android/net/cts/LocalSocketTest.java b/tests/cts/net/src/android/net/cts/LocalSocketTest.java
deleted file mode 100644
index 6e61705..0000000
--- a/tests/cts/net/src/android/net/cts/LocalSocketTest.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2008 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 junit.framework.TestCase;
-
-import android.net.Credentials;
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.system.Os;
-import android.system.OsConstants;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-public class LocalSocketTest extends TestCase {
- private final static String ADDRESS_PREFIX = "com.android.net.LocalSocketTest";
-
- public void testLocalConnections() throws IOException {
- String address = ADDRESS_PREFIX + "_testLocalConnections";
- // create client and server socket
- LocalServerSocket localServerSocket = new LocalServerSocket(address);
- LocalSocket clientSocket = new LocalSocket();
-
- // establish connection between client and server
- LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
- assertFalse(clientSocket.isConnected());
- clientSocket.connect(locSockAddr);
- assertTrue(clientSocket.isConnected());
-
- LocalSocket serverSocket = localServerSocket.accept();
- assertTrue(serverSocket.isConnected());
- assertTrue(serverSocket.isBound());
- try {
- serverSocket.bind(localServerSocket.getLocalSocketAddress());
- fail("Cannot bind a LocalSocket from accept()");
- } catch (IOException expected) {
- }
- try {
- serverSocket.connect(locSockAddr);
- fail("Cannot connect a LocalSocket from accept()");
- } catch (IOException expected) {
- }
-
- Credentials credent = clientSocket.getPeerCredentials();
- assertTrue(0 != credent.getPid());
-
- // send data from client to server
- OutputStream clientOutStream = clientSocket.getOutputStream();
- clientOutStream.write(12);
- InputStream serverInStream = serverSocket.getInputStream();
- assertEquals(12, serverInStream.read());
-
- //send data from server to client
- OutputStream serverOutStream = serverSocket.getOutputStream();
- serverOutStream.write(3);
- InputStream clientInStream = clientSocket.getInputStream();
- assertEquals(3, clientInStream.read());
-
- // Test sending and receiving file descriptors
- clientSocket.setFileDescriptorsForSend(new FileDescriptor[]{FileDescriptor.in});
- clientOutStream.write(32);
- assertEquals(32, serverInStream.read());
-
- FileDescriptor[] out = serverSocket.getAncillaryFileDescriptors();
- assertEquals(1, out.length);
- FileDescriptor fd = clientSocket.getFileDescriptor();
- assertTrue(fd.valid());
-
- //shutdown input stream of client
- clientSocket.shutdownInput();
- assertEquals(-1, clientInStream.read());
-
- //shutdown output stream of client
- clientSocket.shutdownOutput();
- try {
- clientOutStream.write(10);
- fail("testLocalSocket shouldn't come to here");
- } catch (IOException e) {
- // expected
- }
-
- //shutdown input stream of server
- serverSocket.shutdownInput();
- assertEquals(-1, serverInStream.read());
-
- //shutdown output stream of server
- serverSocket.shutdownOutput();
- try {
- serverOutStream.write(10);
- fail("testLocalSocket shouldn't come to here");
- } catch (IOException e) {
- // expected
- }
-
- //close client socket
- clientSocket.close();
- try {
- clientInStream.read();
- fail("testLocalSocket shouldn't come to here");
- } catch (IOException e) {
- // expected
- }
-
- //close server socket
- serverSocket.close();
- try {
- serverInStream.read();
- fail("testLocalSocket shouldn't come to here");
- } catch (IOException e) {
- // expected
- }
- }
-
- public void testAccessors() throws IOException {
- String address = ADDRESS_PREFIX + "_testAccessors";
- LocalSocket socket = new LocalSocket();
- LocalSocketAddress addr = new LocalSocketAddress(address);
-
- assertFalse(socket.isBound());
- socket.bind(addr);
- assertTrue(socket.isBound());
- assertEquals(addr, socket.getLocalSocketAddress());
-
- String str = socket.toString();
- assertTrue(str.contains("impl:android.net.LocalSocketImpl"));
-
- socket.setReceiveBufferSize(1999);
- assertEquals(1999 << 1, socket.getReceiveBufferSize());
-
- socket.setSendBufferSize(3998);
- assertEquals(3998 << 1, socket.getSendBufferSize());
-
- assertEquals(0, socket.getSoTimeout());
- socket.setSoTimeout(1996);
- assertTrue(socket.getSoTimeout() > 0);
-
- try {
- socket.getRemoteSocketAddress();
- fail("testLocalSocketSecondary shouldn't come to here");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- try {
- socket.isClosed();
- fail("testLocalSocketSecondary shouldn't come to here");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- try {
- socket.isInputShutdown();
- fail("testLocalSocketSecondary shouldn't come to here");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- try {
- socket.isOutputShutdown();
- fail("testLocalSocketSecondary shouldn't come to here");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- try {
- socket.connect(addr, 2005);
- fail("testLocalSocketSecondary shouldn't come to here");
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- socket.close();
- }
-
- // http://b/31205169
- public void testSetSoTimeout_readTimeout() throws Exception {
- String address = ADDRESS_PREFIX + "_testSetSoTimeout_readTimeout";
-
- try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
- final LocalSocket clientSocket = socketPair.clientSocket;
-
- // Set the timeout in millis.
- int timeoutMillis = 1000;
- clientSocket.setSoTimeout(timeoutMillis);
-
- // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
- Callable<Result> reader = () -> {
- try {
- clientSocket.getInputStream().read();
- return Result.noException("Did not block");
- } catch (IOException e) {
- return Result.exception(e);
- }
- };
- // Allow the configured timeout, plus some slop.
- int allowedTime = timeoutMillis + 2000;
- Result result = runInSeparateThread(allowedTime, reader);
-
- // Check the message was a timeout, it's all we have to go on.
- String expectedMessage = Os.strerror(OsConstants.EAGAIN);
- result.assertThrewIOException(expectedMessage);
- }
- }
-
- // http://b/31205169
- public void testSetSoTimeout_writeTimeout() throws Exception {
- String address = ADDRESS_PREFIX + "_testSetSoTimeout_writeTimeout";
-
- try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
- final LocalSocket clientSocket = socketPair.clientSocket;
-
- // Set the timeout in millis.
- int timeoutMillis = 1000;
- clientSocket.setSoTimeout(timeoutMillis);
-
- // Set a small buffer size so we know we can flood it.
- clientSocket.setSendBufferSize(100);
- final int bufferSize = clientSocket.getSendBufferSize();
-
- // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
- Callable<Result> writer = () -> {
- try {
- byte[] toWrite = new byte[bufferSize * 2];
- clientSocket.getOutputStream().write(toWrite);
- return Result.noException("Did not block");
- } catch (IOException e) {
- return Result.exception(e);
- }
- };
- // Allow the configured timeout, plus some slop.
- int allowedTime = timeoutMillis + 2000;
-
- Result result = runInSeparateThread(allowedTime, writer);
-
- // Check the message was a timeout, it's all we have to go on.
- String expectedMessage = Os.strerror(OsConstants.EAGAIN);
- result.assertThrewIOException(expectedMessage);
- }
- }
-
- public void testAvailable() throws Exception {
- String address = ADDRESS_PREFIX + "_testAvailable";
-
- try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
- LocalSocket clientSocket = socketPair.clientSocket;
- LocalSocket serverSocket = socketPair.serverSocket.accept();
-
- OutputStream clientOutputStream = clientSocket.getOutputStream();
- InputStream serverInputStream = serverSocket.getInputStream();
- assertEquals(0, serverInputStream.available());
-
- byte[] buffer = new byte[50];
- clientOutputStream.write(buffer);
- assertEquals(50, serverInputStream.available());
-
- InputStream clientInputStream = clientSocket.getInputStream();
- OutputStream serverOutputStream = serverSocket.getOutputStream();
- assertEquals(0, clientInputStream.available());
- serverOutputStream.write(buffer);
- assertEquals(50, serverInputStream.available());
-
- serverSocket.close();
- }
- }
-
- // http://b/34095140
- public void testLocalSocketCreatedFromFileDescriptor() throws Exception {
- String address = ADDRESS_PREFIX + "_testLocalSocketCreatedFromFileDescriptor";
-
- // Establish connection between a local client and server to get a valid client socket file
- // descriptor.
- try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
- // Extract the client FileDescriptor we can use.
- FileDescriptor fileDescriptor = socketPair.clientSocket.getFileDescriptor();
- assertTrue(fileDescriptor.valid());
-
- // Create the LocalSocket we want to test.
- LocalSocket clientSocketCreatedFromFileDescriptor =
- LocalSocket.createConnectedLocalSocket(fileDescriptor);
- assertTrue(clientSocketCreatedFromFileDescriptor.isConnected());
- assertTrue(clientSocketCreatedFromFileDescriptor.isBound());
-
- // Test the LocalSocket can be used for communication.
- LocalSocket serverSocket = socketPair.serverSocket.accept();
- OutputStream clientOutputStream =
- clientSocketCreatedFromFileDescriptor.getOutputStream();
- InputStream serverInputStream = serverSocket.getInputStream();
-
- clientOutputStream.write(12);
- assertEquals(12, serverInputStream.read());
-
- // Closing clientSocketCreatedFromFileDescriptor does not close the file descriptor.
- clientSocketCreatedFromFileDescriptor.close();
- assertTrue(fileDescriptor.valid());
-
- // .. while closing the LocalSocket that owned the file descriptor does.
- socketPair.clientSocket.close();
- assertFalse(fileDescriptor.valid());
- }
- }
-
- public void testFlush() throws Exception {
- String address = ADDRESS_PREFIX + "_testFlush";
-
- try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
- LocalSocket clientSocket = socketPair.clientSocket;
- LocalSocket serverSocket = socketPair.serverSocket.accept();
-
- OutputStream clientOutputStream = clientSocket.getOutputStream();
- InputStream serverInputStream = serverSocket.getInputStream();
- testFlushWorks(clientOutputStream, serverInputStream);
-
- OutputStream serverOutputStream = serverSocket.getOutputStream();
- InputStream clientInputStream = clientSocket.getInputStream();
- testFlushWorks(serverOutputStream, clientInputStream);
-
- serverSocket.close();
- }
- }
-
- private void testFlushWorks(OutputStream outputStream, InputStream inputStream)
- throws Exception {
- final int bytesToTransfer = 50;
- StreamReader inputStreamReader = new StreamReader(inputStream, bytesToTransfer);
-
- byte[] buffer = new byte[bytesToTransfer];
- outputStream.write(buffer);
- assertEquals(bytesToTransfer, inputStream.available());
-
- // Start consuming the data.
- inputStreamReader.start();
-
- // This doesn't actually flush any buffers, it just polls until the reader has read all the
- // bytes.
- outputStream.flush();
-
- inputStreamReader.waitForCompletion(5000);
- inputStreamReader.assertBytesRead(bytesToTransfer);
- assertEquals(0, inputStream.available());
- }
-
- private static class StreamReader extends Thread {
- private final InputStream is;
- private final int expectedByteCount;
- private final CountDownLatch completeLatch = new CountDownLatch(1);
-
- private volatile Exception exception;
- private int bytesRead;
-
- private StreamReader(InputStream is, int expectedByteCount) {
- this.is = is;
- this.expectedByteCount = expectedByteCount;
- }
-
- @Override
- public void run() {
- try {
- byte[] buffer = new byte[10];
- int readCount;
- while ((readCount = is.read(buffer)) >= 0) {
- bytesRead += readCount;
- if (bytesRead >= expectedByteCount) {
- break;
- }
- }
- } catch (IOException e) {
- exception = e;
- } finally {
- completeLatch.countDown();
- }
- }
-
- public void waitForCompletion(long waitMillis) throws Exception {
- if (!completeLatch.await(waitMillis, TimeUnit.MILLISECONDS)) {
- fail("Timeout waiting for completion");
- }
- if (exception != null) {
- throw new Exception("Read failed", exception);
- }
- }
-
- public void assertBytesRead(int expected) {
- assertEquals(expected, bytesRead);
- }
- }
-
- private static class Result {
- private final String type;
- private final Exception e;
-
- private Result(String type, Exception e) {
- this.type = type;
- this.e = e;
- }
-
- static Result noException(String description) {
- return new Result(description, null);
- }
-
- static Result exception(Exception e) {
- return new Result(e.getClass().getName(), e);
- }
-
- void assertThrewIOException(String expectedMessage) {
- assertEquals("Unexpected result type", IOException.class.getName(), type);
- assertEquals("Unexpected exception message", expectedMessage, e.getMessage());
- }
- }
-
- private static Result runInSeparateThread(int allowedTime, final Callable<Result> callable)
- throws Exception {
- ExecutorService service = Executors.newSingleThreadScheduledExecutor();
- Future<Result> future = service.submit(callable);
- Result result = future.get(allowedTime, TimeUnit.MILLISECONDS);
- if (!future.isDone()) {
- fail("Worker thread appears blocked");
- }
- return result;
- }
-
- private static class LocalSocketPair implements AutoCloseable {
- static LocalSocketPair createConnectedSocketPair(String address) throws Exception {
- LocalServerSocket localServerSocket = new LocalServerSocket(address);
- final LocalSocket clientSocket = new LocalSocket();
-
- // Establish connection between client and server
- LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
- clientSocket.connect(locSockAddr);
- assertTrue(clientSocket.isConnected());
- return new LocalSocketPair(localServerSocket, clientSocket);
- }
-
- final LocalServerSocket serverSocket;
- final LocalSocket clientSocket;
-
- LocalSocketPair(LocalServerSocket serverSocket, LocalSocket clientSocket) {
- this.serverSocket = serverSocket;
- this.clientSocket = clientSocket;
- }
-
- public void close() throws Exception {
- serverSocket.close();
- clientSocket.close();
- }
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/MacAddressTest.java b/tests/cts/net/src/android/net/cts/MacAddressTest.java
index 3fd3bba..e47155b 100644
--- a/tests/cts/net/src/android/net/cts/MacAddressTest.java
+++ b/tests/cts/net/src/android/net/cts/MacAddressTest.java
@@ -20,7 +20,7 @@
import static android.net.MacAddress.TYPE_MULTICAST;
import static android.net.MacAddress.TYPE_UNICAST;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
@@ -218,6 +218,6 @@
public void testParcelMacAddress() {
final MacAddress mac = MacAddress.fromString("52:74:f2:b1:a8:7f");
- assertParcelSane(mac, 1);
+ assertParcelingIsLossless(mac);
}
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 1a131d8..ef5dc77 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -61,20 +61,6 @@
import android.net.Uri
import android.net.VpnManager
import android.net.VpnTransportInfo
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback
-import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnValidationStatus
import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnError
import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionAvailable
import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost
@@ -98,6 +84,20 @@
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.Losing
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.TestableNetworkAgent
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
import com.android.testutils.TestableNetworkCallback
import org.junit.After
import org.junit.Assert.assertArrayEquals
@@ -136,10 +136,6 @@
// and then there is the Binder call), so have a short timeout for this as it will be
// exhausted every time.
private const val NO_CALLBACK_TIMEOUT = 200L
-// Any legal score (0~99) for the test network would do, as it is going to be kept up by the
-// requests filed by the test and should never match normal internet requests. 70 is the default
-// score of Ethernet networks, it's as good a value as any other.
-private const val TEST_NETWORK_SCORE = 70
private const val WORSE_NETWORK_SCORE = 65
private const val BETTER_NETWORK_SCORE = 75
private const val FAKE_NET_ID = 1098
@@ -165,10 +161,6 @@
private val mCM = realContext.getSystemService(ConnectivityManager::class.java)!!
private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
private val mFakeConnectivityService = FakeConnectivityService()
-
- private class Provider(context: Context, looper: Looper) :
- NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider")
-
private val agentsToCleanUp = mutableListOf<NetworkAgent>()
private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
private var qosTestSocket: Socket? = null
@@ -219,146 +211,6 @@
fun disconnect() = agent.onDisconnected()
}
- private open class TestableNetworkAgent(
- context: Context,
- looper: Looper,
- val nc: NetworkCapabilities,
- val lp: LinkProperties,
- conf: NetworkAgentConfig
- ) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */,
- nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) {
- private val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
-
- sealed class CallbackEntry {
- object OnBandwidthUpdateRequested : CallbackEntry()
- object OnNetworkUnwanted : CallbackEntry()
- data class OnAddKeepalivePacketFilter(
- val slot: Int,
- val packet: KeepalivePacketData
- ) : CallbackEntry()
- data class OnRemoveKeepalivePacketFilter(val slot: Int) : CallbackEntry()
- data class OnStartSocketKeepalive(
- val slot: Int,
- val interval: Int,
- val packet: KeepalivePacketData
- ) : CallbackEntry()
- data class OnStopSocketKeepalive(val slot: Int) : CallbackEntry()
- data class OnSaveAcceptUnvalidated(val accept: Boolean) : CallbackEntry()
- object OnAutomaticReconnectDisabled : CallbackEntry()
- data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry()
- data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry()
- object OnNetworkCreated : CallbackEntry()
- object OnNetworkDestroyed : CallbackEntry()
- data class OnRegisterQosCallback(
- val callbackId: Int,
- val filter: QosFilter
- ) : CallbackEntry()
- data class OnUnregisterQosCallback(val callbackId: Int) : CallbackEntry()
- }
-
- override fun onBandwidthUpdateRequested() {
- history.add(OnBandwidthUpdateRequested)
- }
-
- override fun onNetworkUnwanted() {
- history.add(OnNetworkUnwanted)
- }
-
- override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) {
- history.add(OnAddKeepalivePacketFilter(slot, packet))
- }
-
- override fun onRemoveKeepalivePacketFilter(slot: Int) {
- history.add(OnRemoveKeepalivePacketFilter(slot))
- }
-
- override fun onStartSocketKeepalive(
- slot: Int,
- interval: Duration,
- packet: KeepalivePacketData
- ) {
- history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet))
- }
-
- override fun onStopSocketKeepalive(slot: Int) {
- history.add(OnStopSocketKeepalive(slot))
- }
-
- override fun onSaveAcceptUnvalidated(accept: Boolean) {
- history.add(OnSaveAcceptUnvalidated(accept))
- }
-
- override fun onAutomaticReconnectDisabled() {
- history.add(OnAutomaticReconnectDisabled)
- }
-
- override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) {
- history.add(OnSignalStrengthThresholdsUpdated(thresholds))
- }
-
- fun expectSignalStrengths(thresholds: IntArray? = intArrayOf()) {
- expectCallback<OnSignalStrengthThresholdsUpdated>().let {
- assertArrayEquals(thresholds, it.thresholds)
- }
- }
-
- override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) {
- history.add(OnRegisterQosCallback(qosCallbackId, filter))
- }
-
- override fun onQosCallbackUnregistered(qosCallbackId: Int) {
- history.add(OnUnregisterQosCallback(qosCallbackId))
- }
-
- override fun onValidationStatus(status: Int, uri: Uri?) {
- history.add(OnValidationStatus(status, uri))
- }
-
- override fun onNetworkCreated() {
- history.add(OnNetworkCreated)
- }
-
- override fun onNetworkDestroyed() {
- history.add(OnNetworkDestroyed)
- }
-
- // Expects the initial validation event that always occurs immediately after registering
- // a NetworkAgent whose network does not require validation (which test networks do
- // not, since they lack the INTERNET capability). It always contains the default argument
- // for the URI.
- fun expectValidationBypassedStatus() = expectCallback<OnValidationStatus>().let {
- assertEquals(it.status, VALID_NETWORK)
- // The returned Uri is parsed from the empty string, which means it's an
- // instance of the (private) Uri.StringUri. There are no real good ways
- // to check this, the least bad is to just convert it to a string and
- // make sure it's empty.
- assertEquals("", it.uri.toString())
- }
-
- inline fun <reified T : CallbackEntry> expectCallback(): T {
- val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
- assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
- return foundCallback
- }
-
- inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) {
- val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
- assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
- assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback")
- }
-
- inline fun <reified T : CallbackEntry> eventuallyExpect() =
- history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also {
- assertNotNull(it, "Callback ${T::class} not received")
- } as T
-
- fun assertNoCallback() {
- assertTrue(waitForIdle(DEFAULT_TIMEOUT_MS),
- "Handler didn't became idle after ${DEFAULT_TIMEOUT_MS}ms")
- assertNull(history.peek())
- }
- }
-
private fun requestNetwork(request: NetworkRequest, callback: TestableNetworkCallback) {
mCM.requestNetwork(request, callback)
callbacksToCleanUp.add(callback)
diff --git a/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt b/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
index dde14ac..391d03a 100644
--- a/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
@@ -19,12 +19,20 @@
import android.Manifest
import android.net.util.NetworkStackUtils
import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
+import android.util.Log
import com.android.testutils.runAsShell
+import com.android.testutils.tryTest
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
/**
* Collection of utility methods for configuring network validation.
*/
internal object NetworkValidationTestUtil {
+ val TAG = NetworkValidationTestUtil::class.simpleName
+ const val TIMEOUT_MS = 20_000L
/**
* Clear the test network validation URLs.
@@ -59,10 +67,52 @@
@JvmStatic fun setUrlExpirationDeviceConfig(timestamp: Long?) =
setConfig(NetworkStackUtils.TEST_URL_EXPIRATION_TIME, timestamp?.toString())
- private fun setConfig(configKey: String, value: String?) {
- runAsShell(Manifest.permission.WRITE_DEVICE_CONFIG) {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_CONNECTIVITY, configKey, value, false /* makeDefault */)
+ private fun setConfig(configKey: String, value: String?): String? {
+ Log.i(TAG, "Setting config \"$configKey\" to \"$value\"")
+ val readWritePermissions = arrayOf(
+ Manifest.permission.READ_DEVICE_CONFIG,
+ Manifest.permission.WRITE_DEVICE_CONFIG)
+
+ val existingValue = runAsShell(*readWritePermissions) {
+ DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, configKey)
+ }
+ if (existingValue == value) {
+ // Already the correct value. There may be a race if a change is already in flight,
+ // but if multiple threads update the config there is no way to fix that anyway.
+ Log.i(TAG, "\$configKey\" already had value \"$value\"")
+ return value
+ }
+
+ val future = CompletableFuture<String>()
+ val listener = DeviceConfig.OnPropertiesChangedListener {
+ // The listener receives updates for any change to any key, so don't react to
+ // changes that do not affect the relevant key
+ if (!it.keyset.contains(configKey)) return@OnPropertiesChangedListener
+ if (it.getString(configKey, null) == value) {
+ future.complete(value)
+ }
+ }
+
+ return tryTest {
+ runAsShell(*readWritePermissions) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_CONNECTIVITY,
+ inlineExecutor,
+ listener)
+ DeviceConfig.setProperty(
+ NAMESPACE_CONNECTIVITY,
+ configKey,
+ value,
+ false /* makeDefault */)
+ // Don't drop the permission until the config is applied, just in case
+ future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }.also {
+ Log.i(TAG, "Config \"$configKey\" successfully set to \"$value\"")
+ }
+ } cleanup {
+ DeviceConfig.removeOnPropertiesChangedListener(listener)
}
}
-}
\ No newline at end of file
+
+ private val inlineExecutor get() = Executor { r -> r.run() }
+}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.java b/tests/cts/net/src/android/net/cts/NsdManagerTest.java
deleted file mode 100644
index 2bcfdc3..0000000
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.java
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * Copyright (C) 2012 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.content.Context;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.Arrays;
-import java.util.Random;
-import java.util.List;
-import java.util.ArrayList;
-
-@AppModeFull(reason = "Socket cannot bind in instant app mode")
-public class NsdManagerTest extends AndroidTestCase {
-
- private static final String TAG = "NsdManagerTest";
- private static final String SERVICE_TYPE = "_nmt._tcp";
- private static final int TIMEOUT = 2000;
-
- private static final boolean DBG = false;
-
- NsdManager mNsdManager;
-
- NsdManager.RegistrationListener mRegistrationListener;
- NsdManager.DiscoveryListener mDiscoveryListener;
- NsdManager.ResolveListener mResolveListener;
- private NsdServiceInfo mResolvedService;
-
- public NsdManagerTest() {
- initRegistrationListener();
- initDiscoveryListener();
- initResolveListener();
- }
-
- private void initRegistrationListener() {
- mRegistrationListener = new NsdManager.RegistrationListener() {
- @Override
- public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
- setEvent("onRegistrationFailed", errorCode);
- }
-
- @Override
- public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
- setEvent("onUnregistrationFailed", errorCode);
- }
-
- @Override
- public void onServiceRegistered(NsdServiceInfo serviceInfo) {
- setEvent("onServiceRegistered", serviceInfo);
- }
-
- @Override
- public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
- setEvent("onServiceUnregistered", serviceInfo);
- }
- };
- }
-
- private void initDiscoveryListener() {
- mDiscoveryListener = new NsdManager.DiscoveryListener() {
- @Override
- public void onStartDiscoveryFailed(String serviceType, int errorCode) {
- setEvent("onStartDiscoveryFailed", errorCode);
- }
-
- @Override
- public void onStopDiscoveryFailed(String serviceType, int errorCode) {
- setEvent("onStopDiscoveryFailed", errorCode);
- }
-
- @Override
- public void onDiscoveryStarted(String serviceType) {
- NsdServiceInfo info = new NsdServiceInfo();
- info.setServiceType(serviceType);
- setEvent("onDiscoveryStarted", info);
- }
-
- @Override
- public void onDiscoveryStopped(String serviceType) {
- NsdServiceInfo info = new NsdServiceInfo();
- info.setServiceType(serviceType);
- setEvent("onDiscoveryStopped", info);
- }
-
- @Override
- public void onServiceFound(NsdServiceInfo serviceInfo) {
- setEvent("onServiceFound", serviceInfo);
- }
-
- @Override
- public void onServiceLost(NsdServiceInfo serviceInfo) {
- setEvent("onServiceLost", serviceInfo);
- }
- };
- }
-
- private void initResolveListener() {
- mResolveListener = new NsdManager.ResolveListener() {
- @Override
- public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
- setEvent("onResolveFailed", errorCode);
- }
-
- @Override
- public void onServiceResolved(NsdServiceInfo serviceInfo) {
- mResolvedService = serviceInfo;
- setEvent("onServiceResolved", serviceInfo);
- }
- };
- }
-
-
-
- private final class EventData {
- EventData(String callbackName, NsdServiceInfo info) {
- mCallbackName = callbackName;
- mSucceeded = true;
- mErrorCode = 0;
- mInfo = info;
- }
- EventData(String callbackName, int errorCode) {
- mCallbackName = callbackName;
- mSucceeded = false;
- mErrorCode = errorCode;
- mInfo = null;
- }
- private final String mCallbackName;
- private final boolean mSucceeded;
- private final int mErrorCode;
- private final NsdServiceInfo mInfo;
- }
-
- private final List<EventData> mEventCache = new ArrayList<EventData>();
-
- private void setEvent(String callbackName, int errorCode) {
- if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode));
- EventData eventData = new EventData(callbackName, errorCode);
- synchronized (mEventCache) {
- mEventCache.add(eventData);
- mEventCache.notify();
- }
- }
-
- private void setEvent(String callbackName, NsdServiceInfo info) {
- if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName());
- EventData eventData = new EventData(callbackName, info);
- synchronized (mEventCache) {
- mEventCache.add(eventData);
- mEventCache.notify();
- }
- }
-
- void clearEventCache() {
- synchronized(mEventCache) {
- mEventCache.clear();
- }
- }
-
- int eventCacheSize() {
- synchronized(mEventCache) {
- return mEventCache.size();
- }
- }
-
- private int mWaitId = 0;
- private EventData waitForCallback(String callbackName) {
-
- synchronized(mEventCache) {
-
- mWaitId ++;
- if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId));
-
- try {
- long startTime = android.os.SystemClock.uptimeMillis();
- long elapsedTime = 0;
- int index = 0;
- while (elapsedTime < TIMEOUT ) {
- // first check if we've received that event
- for (; index < mEventCache.size(); index++) {
- EventData e = mEventCache.get(index);
- if (e.mCallbackName.equals(callbackName)) {
- if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId));
- return e;
- }
- }
-
- // Not yet received, just wait
- mEventCache.wait(TIMEOUT - elapsedTime);
- elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
- }
- // we exited the loop because of TIMEOUT; fail the call
- if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId));
- return null;
- } catch (InterruptedException e) {
- return null; // wait timed out!
- }
- }
- }
-
- private EventData waitForNewEvents() throws InterruptedException {
- if (DBG) Log.d(TAG, "Waiting for a bit, id=" + String.valueOf(mWaitId));
-
- long startTime = android.os.SystemClock.uptimeMillis();
- long elapsedTime = 0;
- synchronized (mEventCache) {
- int index = mEventCache.size();
- while (elapsedTime < TIMEOUT ) {
- // first check if we've received that event
- for (; index < mEventCache.size(); index++) {
- EventData e = mEventCache.get(index);
- return e;
- }
-
- // Not yet received, just wait
- mEventCache.wait(TIMEOUT - elapsedTime);
- elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
- }
- }
-
- return null;
- }
-
- private String mServiceName;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- if (DBG) Log.d(TAG, "Setup test ...");
- mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE);
-
- Random rand = new Random();
- mServiceName = new String("NsdTest");
- for (int i = 0; i < 4; i++) {
- mServiceName = mServiceName + String.valueOf(rand.nextInt(10));
- }
- }
-
- @Override
- public void tearDown() throws Exception {
- if (DBG) Log.d(TAG, "Tear down test ...");
- super.tearDown();
- }
-
- public void testNDSManager() throws Exception {
- EventData lastEvent = null;
-
- if (DBG) Log.d(TAG, "Starting test ...");
-
- NsdServiceInfo si = new NsdServiceInfo();
- si.setServiceType(SERVICE_TYPE);
- si.setServiceName(mServiceName);
-
- byte testByteArray[] = new byte[] {-128, 127, 2, 1, 0, 1, 2};
- String String256 = "1_________2_________3_________4_________5_________6_________" +
- "7_________8_________9_________10________11________12________13________" +
- "14________15________16________17________18________19________20________" +
- "21________22________23________24________25________123456";
-
- // Illegal attributes
- try {
- si.setAttribute(null, (String) null);
- fail("Could set null key");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute("", (String) null);
- fail("Could set empty key");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute(String256, (String) null);
- fail("Could set key with 255 characters");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute("key", String256.substring(3));
- fail("Could set key+value combination with more than 255 characters");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute("key", String256.substring(4));
- fail("Could set key+value combination with 255 characters");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute(new String(new byte[]{0x19}), (String) null);
- fail("Could set key with invalid character");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute("=", (String) null);
- fail("Could set key with invalid character");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- si.setAttribute(new String(new byte[]{0x7F}), (String) null);
- fail("Could set key with invalid character");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- // Allowed attributes
- si.setAttribute("booleanAttr", (String) null);
- si.setAttribute("keyValueAttr", "value");
- si.setAttribute("keyEqualsAttr", "=");
- si.setAttribute(" whiteSpaceKeyValueAttr ", " value ");
- si.setAttribute("binaryDataAttr", testByteArray);
- si.setAttribute("nullBinaryDataAttr", (byte[]) null);
- si.setAttribute("emptyBinaryDataAttr", new byte[]{});
- si.setAttribute("longkey", String256.substring(9));
-
- ServerSocket socket;
- int localPort;
-
- try {
- socket = new ServerSocket(0);
- localPort = socket.getLocalPort();
- si.setPort(localPort);
- } catch (IOException e) {
- if (DBG) Log.d(TAG, "Could not open a local socket");
- assertTrue(false);
- return;
- }
-
- if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort));
-
- clearEventCache();
-
- mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
- lastEvent = waitForCallback("onServiceRegistered"); // id = 1
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
- assertTrue(eventCacheSize() == 1);
-
- // We may not always get the name that we tried to register;
- // This events tells us the name that was registered.
- String registeredName = lastEvent.mInfo.getServiceName();
- si.setServiceName(registeredName);
-
- clearEventCache();
-
- mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
- mDiscoveryListener);
-
- // Expect discovery started
- lastEvent = waitForCallback("onDiscoveryStarted"); // id = 2
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- // Remove this event, so accounting becomes easier later
- synchronized (mEventCache) {
- mEventCache.remove(lastEvent);
- }
-
- // Expect a service record to be discovered (and filter the ones
- // that are unrelated to this test)
- boolean found = false;
- for (int i = 0; i < 32; i++) {
-
- lastEvent = waitForCallback("onServiceFound"); // id = 3
- if (lastEvent == null) {
- // no more onServiceFound events are being reported!
- break;
- }
-
- assertTrue(lastEvent.mSucceeded);
-
- if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
- lastEvent.mInfo.getServiceName());
-
- if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
- // Save it, as it will get overwritten with new serviceFound events
- si = lastEvent.mInfo;
- found = true;
- }
-
- // Remove this event from the event cache, so it won't be found by subsequent
- // calls to waitForCallback
- synchronized (mEventCache) {
- mEventCache.remove(lastEvent);
- }
- }
-
- assertTrue(found);
-
- // We've removed all serviceFound events, and we've removed the discoveryStarted
- // event as well, so now the event cache should be empty!
- assertTrue(eventCacheSize() == 0);
-
- // Resolve the service
- clearEventCache();
- mNsdManager.resolveService(si, mResolveListener);
- lastEvent = waitForCallback("onServiceResolved"); // id = 4
-
- assertNotNull(mResolvedService);
-
- // Check Txt attributes
- assertEquals(8, mResolvedService.getAttributes().size());
- assertTrue(mResolvedService.getAttributes().containsKey("booleanAttr"));
- assertNull(mResolvedService.getAttributes().get("booleanAttr"));
- assertEquals("value", new String(mResolvedService.getAttributes().get("keyValueAttr")));
- assertEquals("=", new String(mResolvedService.getAttributes().get("keyEqualsAttr")));
- assertEquals(" value ", new String(mResolvedService.getAttributes()
- .get(" whiteSpaceKeyValueAttr ")));
- assertEquals(String256.substring(9), new String(mResolvedService.getAttributes()
- .get("longkey")));
- assertTrue(Arrays.equals(testByteArray,
- mResolvedService.getAttributes().get("binaryDataAttr")));
- assertTrue(mResolvedService.getAttributes().containsKey("nullBinaryDataAttr"));
- assertNull(mResolvedService.getAttributes().get("nullBinaryDataAttr"));
- assertTrue(mResolvedService.getAttributes().containsKey("emptyBinaryDataAttr"));
- assertNull(mResolvedService.getAttributes().get("emptyBinaryDataAttr"));
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " +
- String.valueOf(lastEvent.mInfo.getPort()));
-
- assertTrue(lastEvent.mInfo.getPort() == localPort);
- assertTrue(eventCacheSize() == 1);
-
- checkForAdditionalEvents();
- clearEventCache();
-
- // Unregister the service
- mNsdManager.unregisterService(mRegistrationListener);
- lastEvent = waitForCallback("onServiceUnregistered"); // id = 5
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- // Expect a callback for service lost
- lastEvent = waitForCallback("onServiceLost"); // id = 6
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
-
- // Register service again to see if we discover it
- checkForAdditionalEvents();
- clearEventCache();
-
- si = new NsdServiceInfo();
- si.setServiceType(SERVICE_TYPE);
- si.setServiceName(mServiceName);
- si.setPort(localPort);
-
- // Create a new registration listener and register same service again
- initRegistrationListener();
-
- mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
-
- lastEvent = waitForCallback("onServiceRegistered"); // id = 7
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- registeredName = lastEvent.mInfo.getServiceName();
-
- // Expect a record to be discovered
- // Expect a service record to be discovered (and filter the ones
- // that are unrelated to this test)
- found = false;
- for (int i = 0; i < 32; i++) {
-
- lastEvent = waitForCallback("onServiceFound"); // id = 8
- if (lastEvent == null) {
- // no more onServiceFound events are being reported!
- break;
- }
-
- assertTrue(lastEvent.mSucceeded);
-
- if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
- lastEvent.mInfo.getServiceName());
-
- if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
- // Save it, as it will get overwritten with new serviceFound events
- si = lastEvent.mInfo;
- found = true;
- }
-
- // Remove this event from the event cache, so it won't be found by subsequent
- // calls to waitForCallback
- synchronized (mEventCache) {
- mEventCache.remove(lastEvent);
- }
- }
-
- assertTrue(found);
-
- // Resolve the service
- clearEventCache();
- mNsdManager.resolveService(si, mResolveListener);
- lastEvent = waitForCallback("onServiceResolved"); // id = 9
-
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
-
- if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
- lastEvent.mInfo.getServiceName());
-
- assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
-
- assertNotNull(mResolvedService);
-
- // Check that we don't have any TXT records
- assertEquals(0, mResolvedService.getAttributes().size());
-
- checkForAdditionalEvents();
- clearEventCache();
-
- mNsdManager.stopServiceDiscovery(mDiscoveryListener);
- lastEvent = waitForCallback("onDiscoveryStopped"); // id = 10
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
- assertTrue(checkCacheSize(1));
-
- checkForAdditionalEvents();
- clearEventCache();
-
- mNsdManager.unregisterService(mRegistrationListener);
-
- lastEvent = waitForCallback("onServiceUnregistered"); // id = 11
- assertTrue(lastEvent != null);
- assertTrue(lastEvent.mSucceeded);
- assertTrue(checkCacheSize(1));
- }
-
- boolean checkCacheSize(int size) {
- synchronized (mEventCache) {
- int cacheSize = mEventCache.size();
- if (cacheSize != size) {
- Log.d(TAG, "id = " + mWaitId + ": event cache size = " + cacheSize);
- for (int i = 0; i < cacheSize; i++) {
- EventData e = mEventCache.get(i);
- String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
- Log.d(TAG, "eventName is " + e.mCallbackName + sname);
- }
- }
- return (cacheSize == size);
- }
- }
-
- boolean checkForAdditionalEvents() {
- try {
- EventData e = waitForNewEvents();
- if (e != null) {
- String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
- Log.d(TAG, "ignoring unexpected event " + e.mCallbackName + sname);
- }
- return (e == null);
- }
- catch (InterruptedException ex) {
- return false;
- }
- }
-}
-
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
new file mode 100644
index 0000000..9307c27
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2012 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.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
+import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
+import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
+import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
+import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.StartDiscoveryFailed
+import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.StopDiscoveryFailed
+import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
+import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
+import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
+import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.UnregistrationFailed
+import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ResolveFailed
+import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ServiceResolved
+import android.net.nsd.NsdManager
+import android.net.nsd.NsdManager.DiscoveryListener
+import android.net.nsd.NsdManager.RegistrationListener
+import android.net.nsd.NsdManager.ResolveListener
+import android.net.nsd.NsdServiceInfo
+import android.platform.test.annotations.AppModeFull
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.net.module.util.TrackRecord
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.net.ServerSocket
+import java.nio.charset.StandardCharsets
+import java.util.Random
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private const val TAG = "NsdManagerTest"
+private const val SERVICE_TYPE = "_nmt._tcp"
+private const val TIMEOUT_MS = 2000L
+private const val DBG = false
+
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
+@RunWith(AndroidJUnit4::class)
+class NsdManagerTest {
+ private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+ private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
+ private val serviceName = "NsdTest%04d".format(Random().nextInt(1000))
+
+ private interface NsdEvent
+ private open class NsdRecord<T : NsdEvent> private constructor(
+ private val history: ArrayTrackRecord<T>
+ ) : TrackRecord<T> by history {
+ constructor() : this(ArrayTrackRecord())
+
+ val nextEvents = history.newReadHead()
+
+ inline fun <reified V : NsdEvent> expectCallbackEventually(
+ crossinline predicate: (V) -> Boolean = { true }
+ ): V = nextEvents.poll(TIMEOUT_MS) { e -> e is V && predicate(e) } as V?
+ ?: fail("Callback for ${V::class.java.simpleName} not seen after $TIMEOUT_MS ms")
+
+ inline fun <reified V : NsdEvent> expectCallback(): V {
+ val nextEvent = nextEvents.poll(TIMEOUT_MS)
+ assertNotNull(nextEvent, "No callback received after $TIMEOUT_MS ms")
+ assertTrue(nextEvent is V, "Expected ${V::class.java.simpleName} but got " +
+ nextEvent.javaClass.simpleName)
+ return nextEvent
+ }
+ }
+
+ private class NsdRegistrationRecord : RegistrationListener,
+ NsdRecord<NsdRegistrationRecord.RegistrationEvent>() {
+ sealed class RegistrationEvent : NsdEvent {
+ abstract val serviceInfo: NsdServiceInfo
+
+ data class RegistrationFailed(
+ override val serviceInfo: NsdServiceInfo,
+ val errorCode: Int
+ ) : RegistrationEvent()
+
+ data class UnregistrationFailed(
+ override val serviceInfo: NsdServiceInfo,
+ val errorCode: Int
+ ) : RegistrationEvent()
+
+ data class ServiceRegistered(override val serviceInfo: NsdServiceInfo)
+ : RegistrationEvent()
+ data class ServiceUnregistered(override val serviceInfo: NsdServiceInfo)
+ : RegistrationEvent()
+ }
+
+ override fun onRegistrationFailed(si: NsdServiceInfo, err: Int) {
+ add(RegistrationFailed(si, err))
+ }
+
+ override fun onUnregistrationFailed(si: NsdServiceInfo, err: Int) {
+ add(UnregistrationFailed(si, err))
+ }
+
+ override fun onServiceRegistered(si: NsdServiceInfo) {
+ add(ServiceRegistered(si))
+ }
+
+ override fun onServiceUnregistered(si: NsdServiceInfo) {
+ add(ServiceUnregistered(si))
+ }
+ }
+
+ private class NsdDiscoveryRecord : DiscoveryListener,
+ NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>() {
+ sealed class DiscoveryEvent : NsdEvent {
+ data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int)
+ : DiscoveryEvent()
+
+ data class StopDiscoveryFailed(val serviceType: String, val errorCode: Int)
+ : DiscoveryEvent()
+
+ data class DiscoveryStarted(val serviceType: String) : DiscoveryEvent()
+ data class DiscoveryStopped(val serviceType: String) : DiscoveryEvent()
+ data class ServiceFound(val serviceInfo: NsdServiceInfo) : DiscoveryEvent()
+ data class ServiceLost(val serviceInfo: NsdServiceInfo) : DiscoveryEvent()
+ }
+
+ override fun onStartDiscoveryFailed(serviceType: String, err: Int) {
+ add(StartDiscoveryFailed(serviceType, err))
+ }
+
+ override fun onStopDiscoveryFailed(serviceType: String, err: Int) {
+ add(StopDiscoveryFailed(serviceType, err))
+ }
+
+ override fun onDiscoveryStarted(serviceType: String) {
+ add(DiscoveryStarted(serviceType))
+ }
+
+ override fun onDiscoveryStopped(serviceType: String) {
+ add(DiscoveryStopped(serviceType))
+ }
+
+ override fun onServiceFound(si: NsdServiceInfo) {
+ add(ServiceFound(si))
+ }
+
+ override fun onServiceLost(si: NsdServiceInfo) {
+ add(ServiceLost(si))
+ }
+
+ fun waitForServiceDiscovered(serviceName: String): NsdServiceInfo {
+ return expectCallbackEventually<ServiceFound> {
+ it.serviceInfo.serviceName == serviceName
+ }.serviceInfo
+ }
+ }
+
+ private class NsdResolveRecord : ResolveListener,
+ NsdRecord<NsdResolveRecord.ResolveEvent>() {
+ sealed class ResolveEvent : NsdEvent {
+ data class ResolveFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int)
+ : ResolveEvent()
+
+ data class ServiceResolved(val serviceInfo: NsdServiceInfo) : ResolveEvent()
+ }
+
+ override fun onResolveFailed(si: NsdServiceInfo, err: Int) {
+ add(ResolveFailed(si, err))
+ }
+
+ override fun onServiceResolved(si: NsdServiceInfo) {
+ add(ServiceResolved(si))
+ }
+ }
+
+ @Test
+ fun testNsdManager() {
+ val si = NsdServiceInfo()
+ si.serviceType = SERVICE_TYPE
+ si.serviceName = serviceName
+ // Test binary data with various bytes
+ val testByteArray = byteArrayOf(-128, 127, 2, 1, 0, 1, 2)
+ // Test string data with 256 characters (25 blocks of 10 characters + 6)
+ val string256 = "1_________2_________3_________4_________5_________6_________" +
+ "7_________8_________9_________10________11________12________13________" +
+ "14________15________16________17________18________19________20________" +
+ "21________22________23________24________25________123456"
+
+ // Illegal attributes
+ listOf(
+ Triple(null, null, "null key"),
+ Triple("", null, "empty key"),
+ Triple(string256, null, "key with 256 characters"),
+ Triple("key", string256.substring(3),
+ "key+value combination with more than 255 characters"),
+ Triple("key", string256.substring(4), "key+value combination with 255 characters"),
+ Triple("\u0019", null, "key with invalid character"),
+ Triple("=", null, "key with invalid character"),
+ Triple("\u007f", null, "key with invalid character")
+ ).forEach {
+ assertFailsWith<IllegalArgumentException>(
+ "Setting invalid ${it.third} unexpectedly succeeded") {
+ si.setAttribute(it.first, it.second)
+ }
+ }
+
+ // Allowed attributes
+ si.setAttribute("booleanAttr", null as String?)
+ si.setAttribute("keyValueAttr", "value")
+ si.setAttribute("keyEqualsAttr", "=")
+ si.setAttribute(" whiteSpaceKeyValueAttr ", " value ")
+ si.setAttribute("binaryDataAttr", testByteArray)
+ si.setAttribute("nullBinaryDataAttr", null as ByteArray?)
+ si.setAttribute("emptyBinaryDataAttr", byteArrayOf())
+ si.setAttribute("longkey", string256.substring(9))
+ val socket = ServerSocket(0)
+ val localPort = socket.localPort
+ si.port = localPort
+ if (DBG) Log.d(TAG, "Port = $localPort")
+
+ val registrationRecord = NsdRegistrationRecord()
+ val registeredInfo = registerService(registrationRecord, si)
+
+ val discoveryRecord = NsdDiscoveryRecord()
+ nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+
+ // Expect discovery started
+ discoveryRecord.expectCallback<DiscoveryStarted>()
+
+ // Expect a service record to be discovered
+ val foundInfo = discoveryRecord.waitForServiceDiscovered(registeredInfo.serviceName)
+
+ val resolvedService = resolveService(foundInfo)
+
+ // Check Txt attributes
+ assertEquals(8, resolvedService.attributes.size)
+ assertTrue(resolvedService.attributes.containsKey("booleanAttr"))
+ assertNull(resolvedService.attributes["booleanAttr"])
+ assertEquals("value", resolvedService.attributes["keyValueAttr"].utf8ToString())
+ assertEquals("=", resolvedService.attributes["keyEqualsAttr"].utf8ToString())
+ assertEquals(" value ",
+ resolvedService.attributes[" whiteSpaceKeyValueAttr "].utf8ToString())
+ assertEquals(string256.substring(9), resolvedService.attributes["longkey"].utf8ToString())
+ assertArrayEquals(testByteArray, resolvedService.attributes["binaryDataAttr"])
+ assertTrue(resolvedService.attributes.containsKey("nullBinaryDataAttr"))
+ assertNull(resolvedService.attributes["nullBinaryDataAttr"])
+ assertTrue(resolvedService.attributes.containsKey("emptyBinaryDataAttr"))
+ assertNull(resolvedService.attributes["emptyBinaryDataAttr"])
+ assertEquals(localPort, resolvedService.port)
+
+ // Unregister the service
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+
+ // Expect a callback for service lost
+ discoveryRecord.expectCallbackEventually<ServiceLost> {
+ it.serviceInfo.serviceName == serviceName
+ }
+
+ // Register service again to see if NsdManager can discover it
+ val si2 = NsdServiceInfo()
+ si2.serviceType = SERVICE_TYPE
+ si2.serviceName = serviceName
+ si2.port = localPort
+ val registrationRecord2 = NsdRegistrationRecord()
+ val registeredInfo2 = registerService(registrationRecord2, si2)
+
+ // Expect a service record to be discovered (and filter the ones
+ // that are unrelated to this test)
+ val foundInfo2 = discoveryRecord.waitForServiceDiscovered(registeredInfo2.serviceName)
+
+ // Resolve the service
+ val resolvedService2 = resolveService(foundInfo2)
+
+ // Check that the resolved service doesn't have any TXT records
+ assertEquals(0, resolvedService2.attributes.size)
+
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+
+ discoveryRecord.expectCallbackEventually<DiscoveryStopped>()
+
+ nsdManager.unregisterService(registrationRecord2)
+ registrationRecord2.expectCallback<ServiceUnregistered>()
+ }
+
+ /**
+ * Register a service and return its registration record.
+ */
+ private fun registerService(record: NsdRegistrationRecord, si: NsdServiceInfo): NsdServiceInfo {
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record)
+ // We may not always get the name that we tried to register;
+ // This events tells us the name that was registered.
+ val cb = record.expectCallback<ServiceRegistered>()
+ return cb.serviceInfo
+ }
+
+ private fun resolveService(discoveredInfo: NsdServiceInfo): NsdServiceInfo {
+ val record = NsdResolveRecord()
+ nsdManager.resolveService(discoveredInfo, record)
+ val resolvedCb = record.expectCallback<ServiceResolved>()
+ assertEquals(discoveredInfo.serviceName, resolvedCb.serviceInfo.serviceName)
+
+ return resolvedCb.serviceInfo
+ }
+}
+
+private fun ByteArray?.utf8ToString(): String {
+ if (this == null) return ""
+ return String(this, StandardCharsets.UTF_8)
+}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 05e40f9..7254319 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -380,6 +380,12 @@
return mCellNetworkCallback != null;
}
+ public void tearDown() {
+ if (cellConnectAttempted()) {
+ disconnectFromCell();
+ }
+ }
+
private NetworkRequest makeWifiNetworkRequest() {
return new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
@@ -476,6 +482,7 @@
NetworkCallback callback = new NetworkCallback() {
@Override
public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
+ Log.i(TAG, "Link properties of network " + n + " changed to " + lp);
if (requiresValidatedServer && lp.getValidatedPrivateDnsServers().isEmpty()) {
return;
}
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
new file mode 100644
index 0000000..2c44010
--- /dev/null
+++ b/tests/mts/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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "bpf_existence_test",
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
+ defaults: [
+ "connectivity-mainline-presubmit-cc-defaults",
+ ],
+ 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..142e013
--- /dev/null
+++ b/tests/mts/bpf_existence_test.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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 has expected BPF programs and maps
+ */
+
+#include <cstdint>
+#include <set>
+#include <string>
+
+#include <android/api-level.h>
+#include <android-base/properties.h>
+#include <android-modules-utils/sdk_level.h>
+
+#include <gtest/gtest.h>
+
+using std::find;
+using std::set;
+using std::string;
+
+using android::modules::sdklevel::IsAtLeastR;
+using android::modules::sdklevel::IsAtLeastS;
+using android::modules::sdklevel::IsAtLeastT;
+
+// Mainline development branches lack the constant for the current development OS.
+#ifndef __ANDROID_API_T__
+#define __ANDROID_API_T__ 33
+#endif
+
+#define PLATFORM "/sys/fs/bpf/"
+#define TETHERING "/sys/fs/bpf/tethering/"
+
+class BpfExistenceTest : public ::testing::Test {
+};
+
+static const set<string> INTRODUCED_R = {
+ PLATFORM "map_offload_tether_ingress_map",
+ PLATFORM "map_offload_tether_limit_map",
+ PLATFORM "map_offload_tether_stats_map",
+ PLATFORM "prog_offload_schedcls_ingress_tether_ether",
+ PLATFORM "prog_offload_schedcls_ingress_tether_rawip",
+};
+
+static const set<string> INTRODUCED_S = {
+ TETHERING "map_offload_tether_dev_map",
+ TETHERING "map_offload_tether_downstream4_map",
+ TETHERING "map_offload_tether_downstream64_map",
+ TETHERING "map_offload_tether_downstream6_map",
+ TETHERING "map_offload_tether_error_map",
+ TETHERING "map_offload_tether_limit_map",
+ TETHERING "map_offload_tether_stats_map",
+ TETHERING "map_offload_tether_upstream4_map",
+ TETHERING "map_offload_tether_upstream6_map",
+ TETHERING "map_test_tether_downstream6_map",
+ TETHERING "prog_offload_schedcls_tether_downstream4_ether",
+ TETHERING "prog_offload_schedcls_tether_downstream4_rawip",
+ TETHERING "prog_offload_schedcls_tether_downstream6_ether",
+ TETHERING "prog_offload_schedcls_tether_downstream6_rawip",
+ TETHERING "prog_offload_schedcls_tether_upstream4_ether",
+ TETHERING "prog_offload_schedcls_tether_upstream4_rawip",
+ TETHERING "prog_offload_schedcls_tether_upstream6_ether",
+ TETHERING "prog_offload_schedcls_tether_upstream6_rawip",
+};
+
+static const set<string> REMOVED_S = {
+ PLATFORM "map_offload_tether_ingress_map",
+ PLATFORM "map_offload_tether_limit_map",
+ PLATFORM "map_offload_tether_stats_map",
+ PLATFORM "prog_offload_schedcls_ingress_tether_ether",
+ PLATFORM "prog_offload_schedcls_ingress_tether_rawip",
+};
+
+static const set<string> INTRODUCED_T = {
+};
+
+static const set<string> REMOVED_T = {
+};
+
+void addAll(set<string>* a, const set<string>& b) {
+ a->insert(b.begin(), b.end());
+}
+
+void removeAll(set<string>* a, const set<string>& b) {
+ for (const auto& toRemove : b) {
+ a->erase(toRemove);
+ }
+}
+
+void getFileLists(set<string>* expected, set<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() {
+ set<string> mustExist;
+ set<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_get_device_api_level();
+ ASSERT_NE(0, buildVersionSdk) << "Unable to determine device SDK version";
+ if (buildVersionSdk > __ANDROID_API_T__ && buildVersionSdk != __ANDROID_API_FUTURE__) {
+ 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/Android.bp b/tests/unit/Android.bp
index bd30b77..17133c7 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -25,7 +25,7 @@
"ld-android",
"libbase",
"libbinder",
- "libbpf",
+ "libbpf_bcc",
"libbpf_android",
"libc++",
"libcgrouprc",
@@ -38,8 +38,8 @@
"liblog",
"liblzma",
"libnativehelper",
- "libnetdbpf",
"libnetdutils",
+ "libnetworkstats",
"libnetworkstatsfactorytestjni",
"libpackagelistparser",
"libpcre2",
@@ -69,12 +69,12 @@
"java/android/net/IpSecTransformTest.java",
"java/android/net/KeepalivePacketDataUtilTest.java",
"java/android/net/NetworkIdentityTest.kt",
- "java/android/net/NetworkStatsTest.java",
- "java/android/net/NetworkStatsHistoryTest.java",
+ "java/android/net/NetworkStats*.java",
"java/android/net/NetworkTemplateTest.kt",
"java/android/net/TelephonyNetworkSpecifierTest.java",
"java/android/net/VpnManagerTest.java",
"java/android/net/ipmemorystore/*.java",
+ "java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
"java/android/net/nsd/*.java",
"java/com/android/internal/net/NetworkUtilsInternalTest.java",
"java/com/android/internal/net/VpnProfileTest.java",
@@ -89,7 +89,9 @@
"java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
"java/com/android/server/connectivity/VpnTest.java",
"java/com/android/server/net/ipmemorystore/*.java",
+ "java/com/android/server/net/BpfInterfaceMapUpdaterTest.java",
"java/com/android/server/net/NetworkStats*.java",
+ "java/com/android/server/net/TestableUsageCallback.kt",
]
}
@@ -131,6 +133,7 @@
"platform-compat-test-rules",
"platform-test-annotations",
"service-connectivity-pre-jarjar",
+ "service-connectivity-tiramisu-pre-jarjar",
"services.core-vpn",
],
libs: [
@@ -141,6 +144,7 @@
"ServiceConnectivityResources",
],
visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
+ exclude_kotlinc_generated_files: false,
}
android_test {
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index 08a3007..561e621 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -220,6 +220,47 @@
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());
+ }
+
+
+ @Test
+ public void testQueryDetailsForDevice() throws Exception {
+ final long startTime = 1;
+ final long endTime = 100;
+
+ reset(mStatsSession);
+ when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
+ when(mStatsSession.getHistoryIntervalForNetwork(any(NetworkTemplate.class),
+ anyInt(), anyLong(), anyLong()))
+ .thenReturn(new NetworkStatsHistory(10, 0));
+ final NetworkTemplate template = new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
+ .setMeteredness(NetworkStats.Bucket.METERED_YES).build();
+ NetworkStats stats = mManager.queryDetailsForDevice(template, startTime, endTime);
+
+ verify(mStatsSession, times(1)).getHistoryIntervalForNetwork(
+ eq(template), eq(NetworkStatsHistory.FIELD_ALL), 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());
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index 56e5c62..a151f03 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -35,6 +35,7 @@
import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,6 +65,9 @@
private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
private static final int TEST_MTU = 1300;
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
private final MockContext mMockContext =
new MockContext() {
@Override
@@ -259,6 +263,28 @@
}
}
+
+ // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP
+ @DevSdkIgnoreRule.IgnoreUpTo(32)
+ @Test
+ public void testBuildExcludeLocalRoutesSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+ builder.setExcludeLocalRoutes(true);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+ assertTrue(profile.getExcludeLocalRoutes());
+
+ builder.setBypassable(false);
+ try {
+ builder.build();
+ fail("Expected exception because excludeLocalRoutes should be set only"
+ + " on the bypassable VPN");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
@Test
public void testBuildInvalidMtu() throws Exception {
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index b1ffc92..4b2d874 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -17,11 +17,16 @@
package android.net
import android.content.Context
+import android.net.ConnectivityManager.MAX_NETWORK_TYPE
+import android.net.ConnectivityManager.TYPE_ETHERNET
import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_NONE
+import android.net.ConnectivityManager.TYPE_WIFI
import android.net.NetworkIdentity.OEM_NONE
import android.net.NetworkIdentity.OEM_PAID
import android.net.NetworkIdentity.OEM_PRIVATE
import android.net.NetworkIdentity.getOemBitfield
+import android.app.usage.NetworkStatsManager
import android.telephony.TelephonyManager
import android.os.Build
import com.android.testutils.DevSdkIgnoreRule
@@ -30,10 +35,12 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
private const val TEST_IMSI = "testimsi"
+private const val TEST_WIFI_KEY = "testwifikey"
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
@@ -74,12 +81,12 @@
}
@Test
- fun testGetMetered() {
+ fun testIsMetered() {
// Verify network is metered.
val netIdent1 = NetworkIdentity.buildNetworkIdentity(mockContext,
buildMobileNetworkStateSnapshot(NetworkCapabilities(), TEST_IMSI),
false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
- assertTrue(netIdent1.getMetered())
+ assertTrue(netIdent1.isMetered())
// Verify network is not metered because it has NET_CAPABILITY_NOT_METERED capability.
val capsNotMetered = NetworkCapabilities.Builder().apply {
@@ -88,7 +95,7 @@
val netIdent2 = NetworkIdentity.buildNetworkIdentity(mockContext,
buildMobileNetworkStateSnapshot(capsNotMetered, TEST_IMSI),
false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
- assertFalse(netIdent2.getMetered())
+ assertFalse(netIdent2.isMetered())
// Verify network is not metered because it has NET_CAPABILITY_TEMPORARILY_NOT_METERED
// capability .
@@ -98,6 +105,121 @@
val netIdent3 = NetworkIdentity.buildNetworkIdentity(mockContext,
buildMobileNetworkStateSnapshot(capsTempNotMetered, TEST_IMSI),
false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
- assertFalse(netIdent3.getMetered())
+ assertFalse(netIdent3.isMetered())
+ }
+
+ @Test
+ fun testBuilder() {
+ val oemPrivateRoamingNotMeteredCap = NetworkCapabilities().apply {
+ addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ }
+ val identFromSnapshot = NetworkIdentity.Builder().setNetworkStateSnapshot(
+ buildMobileNetworkStateSnapshot(oemPrivateRoamingNotMeteredCap, TEST_IMSI))
+ .setDefaultNetwork(true)
+ .setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
+ .build()
+ val identFromLegacyBuild = NetworkIdentity.buildNetworkIdentity(mockContext,
+ buildMobileNetworkStateSnapshot(oemPrivateRoamingNotMeteredCap, TEST_IMSI),
+ true /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+ val identFromConstructor = NetworkIdentity(TYPE_MOBILE,
+ TelephonyManager.NETWORK_TYPE_UMTS,
+ TEST_IMSI,
+ null /* wifiNetworkKey */,
+ true /* roaming */,
+ false /* metered */,
+ true /* defaultNetwork */,
+ NetworkTemplate.OEM_MANAGED_PRIVATE)
+ assertEquals(identFromLegacyBuild, identFromSnapshot)
+ assertEquals(identFromConstructor, identFromSnapshot)
+
+ // Assert non-wifi can't have wifi network key.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_ETHERNET)
+ .setWifiNetworkKey(TEST_WIFI_KEY)
+ .build()
+ }
+
+ // Assert non-mobile can't have ratType.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_WIFI)
+ .setRatType(TelephonyManager.NETWORK_TYPE_LTE)
+ .build()
+ }
+ }
+
+ @Test
+ fun testBuilder_type() {
+ // Assert illegal type values cannot make an identity.
+ listOf(Integer.MIN_VALUE, TYPE_NONE - 1, MAX_NETWORK_TYPE + 1, Integer.MAX_VALUE)
+ .forEach { type ->
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder().setType(type).build()
+ }
+ }
+
+ // Verify legitimate type values can make an identity.
+ for (type in TYPE_NONE..MAX_NETWORK_TYPE) {
+ NetworkIdentity.Builder().setType(type).build().also {
+ assertEquals(it.type, type)
+ }
+ }
+ }
+
+ @Test
+ fun testBuilder_ratType() {
+ // Assert illegal ratTypes cannot make an identity.
+ listOf(Integer.MIN_VALUE, NetworkTemplate.NETWORK_TYPE_ALL,
+ NetworkStatsManager.NETWORK_TYPE_5G_NSA - 1, Integer.MAX_VALUE)
+ .forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_MOBILE)
+ .setRatType(it)
+ .build()
+ }
+ }
+
+ // Verify legitimate ratTypes can make an identity.
+ TelephonyManager.getAllNetworkTypes().toMutableList().also {
+ it.add(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+ it.add(NetworkStatsManager.NETWORK_TYPE_5G_NSA)
+ }.forEach { rat ->
+ NetworkIdentity.Builder()
+ .setType(TYPE_MOBILE)
+ .setRatType(rat)
+ .build().also {
+ assertEquals(it.ratType, rat)
+ }
+ }
+ }
+
+ @Test
+ fun testBuilder_oemManaged() {
+ // Assert illegal oemManage values cannot make an identity.
+ listOf(Integer.MIN_VALUE, NetworkTemplate.OEM_MANAGED_ALL, NetworkTemplate.OEM_MANAGED_YES,
+ Integer.MAX_VALUE)
+ .forEach { oemManaged ->
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_MOBILE)
+ .setOemManaged(oemManaged)
+ .build()
+ }
+ }
+
+ // Verify legitimate oem managed values can make an identity.
+ listOf(NetworkTemplate.OEM_MANAGED_NO, NetworkTemplate.OEM_MANAGED_PAID,
+ NetworkTemplate.OEM_MANAGED_PRIVATE, NetworkTemplate.OEM_MANAGED_PAID or
+ NetworkTemplate.OEM_MANAGED_PRIVATE)
+ .forEach { oemManaged ->
+ NetworkIdentity.Builder()
+ .setOemManaged(oemManaged)
+ .build().also {
+ assertEquals(it.oemManaged, oemManaged)
+ }
+ }
}
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/unit/java/android/net/NetworkStatsAccessTest.java
similarity index 69%
rename from tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java
rename to tests/unit/java/android/net/NetworkStatsAccessTest.java
index 03d9404..97a93ca 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/android/net/NetworkStatsAccessTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,10 +11,12 @@
* 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
+ * limitations under the License.
*/
-package com.android.server.net;
+package android.net;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
@@ -22,15 +24,13 @@
import android.Manifest;
import android.Manifest.permission;
import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Build;
import android.telephony.TelephonyManager;
import androidx.test.filters.SmallTest;
-import com.android.server.LocalServices;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -43,123 +43,112 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public class NetworkStatsAccessTest {
private static final String TEST_PKG = "com.example.test";
+ private static final int TEST_PID = 1234;
private static final int TEST_UID = 12345;
@Mock private Context mContext;
- @Mock private DevicePolicyManagerInternal mDpmi;
+ @Mock private DevicePolicyManager mDpm;
@Mock private TelephonyManager mTm;
@Mock private AppOpsManager mAppOps;
// Hold the real service so we can restore it when tearing down the test.
- private DevicePolicyManagerInternal mSystemDpmi;
+ private DevicePolicyManager mSystemDpm;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
- LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
- LocalServices.addService(DevicePolicyManagerInternal.class, mDpmi);
-
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTm);
when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
+ when(mContext.getSystemServiceName(DevicePolicyManager.class))
+ .thenReturn(Context.DEVICE_POLICY_SERVICE);
+ when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDpm);
+
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(false);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(false);
+ setHasNetworkStackPermission(false);
}
@After
public void tearDown() throws Exception {
- LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
- LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
}
@Test
public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
setHasCarrierPrivileges(true);
- setIsDeviceOwner(false);
- setIsProfileOwner(false);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
- setHasReadHistoryPermission(false);
assertEquals(NetworkStatsAccess.Level.DEVICE,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
}
@Test
public void testCheckAccessLevel_isDeviceOwner() throws Exception {
- setHasCarrierPrivileges(false);
setIsDeviceOwner(true);
- setIsProfileOwner(false);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
- setHasReadHistoryPermission(false);
assertEquals(NetworkStatsAccess.Level.DEVICE,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
}
@Test
public void testCheckAccessLevel_isProfileOwner() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
setIsProfileOwner(true);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
- setHasReadHistoryPermission(false);
assertEquals(NetworkStatsAccess.Level.USER,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
}
@Test
public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
setIsProfileOwner(true);
setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
- setHasReadHistoryPermission(false);
assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
}
@Test
public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
setIsProfileOwner(true);
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
- setHasReadHistoryPermission(false);
assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
}
@Test
public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
setIsProfileOwner(true);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
setHasReadHistoryPermission(true);
assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
}
@Test
public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
- setIsProfileOwner(false);
setHasAppOpsPermission(AppOpsManager.MODE_ERRORED, true);
- setHasReadHistoryPermission(false);
assertEquals(NetworkStatsAccess.Level.DEFAULT,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
}
@Test
public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
- setIsProfileOwner(false);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
- setHasReadHistoryPermission(false);
assertEquals(NetworkStatsAccess.Level.DEFAULT,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
+ }
+
+ @Test
+ public void testCheckAccessLevel_hasNetworkStackPermission() throws Exception {
+ assertEquals(NetworkStatsAccess.Level.DEFAULT,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
+
+ setHasNetworkStackPermission(true);
+ assertEquals(NetworkStatsAccess.Level.DEVICE,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
+
+ setHasNetworkStackPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEFAULT,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
}
private void setHasCarrierPrivileges(boolean hasPrivileges) {
@@ -169,16 +158,16 @@
}
private void setIsDeviceOwner(boolean isOwner) {
- when(mDpmi.isActiveDeviceOwner(TEST_UID)).thenReturn(isOwner);
+ when(mDpm.isDeviceOwnerApp(TEST_PKG)).thenReturn(isOwner);
}
private void setIsProfileOwner(boolean isOwner) {
- when(mDpmi.isActiveProfileOwner(TEST_UID)).thenReturn(isOwner);
+ when(mDpm.isProfileOwnerApp(TEST_PKG)).thenReturn(isOwner);
}
private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) {
- when(mAppOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG))
- .thenReturn(appOpsMode);
+ when(mAppOps.noteOp(AppOpsManager.OPSTR_GET_USAGE_STATS, TEST_UID, TEST_PKG,
+ null /* attributionTag */, null /* message */)).thenReturn(appOpsMode);
when(mContext.checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn(
hasPermission ? PackageManager.PERMISSION_GRANTED
: PackageManager.PERMISSION_DENIED);
@@ -189,4 +178,10 @@
.thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
: PackageManager.PERMISSION_DENIED);
}
+
+ private void setHasNetworkStackPermission(boolean hasPermission) {
+ when(mContext.checkPermission(android.Manifest.permission.NETWORK_STACK,
+ TEST_PID, TEST_UID)).thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED);
+ }
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
similarity index 88%
rename from tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java
rename to tests/unit/java/android/net/NetworkStatsCollectionTest.java
index 6b4ead5..c27ee93 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.net;
+package android.net;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkIdentity.OEM_NONE;
@@ -28,7 +28,8 @@
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational;
+import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertArrayEquals;
@@ -37,17 +38,13 @@
import static org.junit.Assert.fail;
import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.NetworkIdentity;
-import android.net.NetworkStats;
-import android.net.NetworkStatsHistory;
-import android.net.NetworkTemplate;
-import android.os.Build;
+import android.net.NetworkStatsCollection.Key;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.RecurrenceRule;
import androidx.test.InstrumentationRegistry;
@@ -64,6 +61,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -77,13 +75,14 @@
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Tests for {@link NetworkStatsCollection}.
*/
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public class NetworkStatsCollectionTest {
private static final String TEST_FILE = "test.bin";
@@ -200,8 +199,8 @@
// record empty data straddling between buckets
final NetworkStats.Entry entry = new NetworkStats.Entry();
entry.rxBytes = 32;
- collection.recordData(null, UID_ALL, SET_DEFAULT, TAG_NONE, 30 * MINUTE_IN_MILLIS,
- 90 * MINUTE_IN_MILLIS, entry);
+ collection.recordData(Mockito.mock(NetworkIdentitySet.class), UID_ALL, SET_DEFAULT,
+ TAG_NONE, 30 * MINUTE_IN_MILLIS, 90 * MINUTE_IN_MILLIS, entry);
// assert that we report boundary in atomic buckets
assertEquals(0, collection.getStartMillis());
@@ -250,8 +249,8 @@
collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
// Verify security check in getHistory.
- assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null, myUid, SET_DEFAULT,
- TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid));
+ assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null,
+ myUid, SET_DEFAULT, TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid));
try {
collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null, otherUidInSameUser,
SET_DEFAULT, TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid);
@@ -275,7 +274,8 @@
new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
stageFile(R.raw.netstats_v1, testFile);
- final NetworkStatsCollection emptyCollection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ final NetworkStatsCollection emptyCollection =
+ new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
collection.readLegacyNetwork(testFile);
@@ -313,7 +313,8 @@
assertEquals(0L, history.getTotalBytes());
// Normal collection should be untouched
- history = getHistory(collection, plan, TIME_A, TIME_C); i = 0;
+ history = getHistory(collection, plan, TIME_A, TIME_C);
+ i = 0;
assertEntry(100647, 197, 23649, 185, history.getValues(i++, null));
assertEntry(100647, 196, 23648, 185, history.getValues(i++, null));
assertEntry(18323, 76, 15032, 76, history.getValues(i++, null));
@@ -342,7 +343,8 @@
// Slice from middle should be untouched
history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
- TIME_B + HOUR_IN_MILLIS); i = 0;
+ TIME_B + HOUR_IN_MILLIS);
+ i = 0;
assertEntry(3821, 23, 4525, 26, history.getValues(i++, null));
assertEntry(3820, 21, 4524, 26, history.getValues(i++, null));
assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
@@ -365,7 +367,8 @@
assertEquals(200000L, history.getTotalBytes());
// Normal collection should be augmented
- history = getHistory(collection, plan, TIME_A, TIME_C); i = 0;
+ history = getHistory(collection, plan, TIME_A, TIME_C);
+ i = 0;
assertEntry(100647, 197, 23649, 185, history.getValues(i++, null));
assertEntry(100647, 196, 23648, 185, history.getValues(i++, null));
assertEntry(18323, 76, 15032, 76, history.getValues(i++, null));
@@ -397,7 +400,8 @@
// Slice from middle should be augmented
history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
- TIME_B + HOUR_IN_MILLIS); i = 0;
+ TIME_B + HOUR_IN_MILLIS);
+ i = 0;
assertEntry(2669, 0, 3161, 0, history.getValues(i++, null));
assertEntry(2668, 0, 3160, 0, history.getValues(i++, null));
assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
@@ -420,7 +424,8 @@
assertEquals(400000L, history.getTotalBytes());
// Normal collection should be augmented
- history = getHistory(collection, plan, TIME_A, TIME_C); i = 0;
+ history = getHistory(collection, plan, TIME_A, TIME_C);
+ i = 0;
assertEntry(100647, 197, 23649, 185, history.getValues(i++, null));
assertEntry(100647, 196, 23648, 185, history.getValues(i++, null));
assertEntry(18323, 76, 15032, 76, history.getValues(i++, null));
@@ -451,7 +456,8 @@
// Slice from middle should be augmented
history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
- TIME_B + HOUR_IN_MILLIS); i = 0;
+ TIME_B + HOUR_IN_MILLIS);
+ i = 0;
assertEntry(5338, 0, 6322, 0, history.getValues(i++, null));
assertEntry(5337, 0, 6320, 0, history.getValues(i++, null));
assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
@@ -527,6 +533,52 @@
assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0));
}
+ @Test
+ public void testBuilder() {
+ final Map<Key, NetworkStatsHistory> expectedEntries = new ArrayMap<>();
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final NetworkIdentitySet ident = new NetworkIdentitySet();
+ final Key key1 = new Key(ident, 0, 0, 0);
+ final Key key2 = new Key(ident, 1, 0, 0);
+ final long bucketDuration = 10;
+
+ final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 10, 40,
+ 4, 50, 5, 60);
+ final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(30, 10, 3,
+ 41, 7, 1, 0);
+
+ NetworkStatsHistory history1 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .build();
+
+ NetworkStatsHistory history2 = new NetworkStatsHistory(10, 5);
+
+ NetworkStatsCollection actualCollection = new NetworkStatsCollection.Builder(bucketDuration)
+ .addEntry(key1, history1)
+ .addEntry(key2, history2)
+ .build();
+
+ // The builder will omit any entry with empty history. Thus, history2
+ // is not expected in the result collection.
+ expectedEntries.put(key1, history1);
+
+ final Map<Key, NetworkStatsHistory> actualEntries = actualCollection.getEntries();
+
+ assertEquals(expectedEntries.size(), actualEntries.size());
+ for (Key expectedKey : expectedEntries.keySet()) {
+ final NetworkStatsHistory expectedHistory = expectedEntries.get(expectedKey);
+
+ final NetworkStatsHistory actualHistory = actualEntries.get(expectedKey);
+ assertNotNull(actualHistory);
+
+ assertEquals(expectedHistory.getEntries(), actualHistory.getEntries());
+
+ actualEntries.remove(expectedKey);
+ }
+ assertEquals(0, actualEntries.size());
+ }
+
/**
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
* testing purposes.
@@ -584,6 +636,14 @@
actual.txBytes, actual.txPackets, 0L));
}
+ private static void assertEntry(NetworkStatsHistory.Entry expected,
+ NetworkStatsHistory.Entry actual) {
+ assertEntry(new NetworkStats.Entry(actual.rxBytes, actual.rxPackets,
+ actual.txBytes, actual.txPackets, 0L),
+ new NetworkStats.Entry(actual.rxBytes, actual.rxPackets,
+ actual.txBytes, actual.txPackets, 0L));
+ }
+
private static void assertEntry(NetworkStats.Entry expected,
NetworkStats.Entry actual) {
assertEquals("unexpected rxBytes", expected.rxBytes, actual.rxBytes);
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index c5f8c00..c170605 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -56,6 +56,7 @@
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
+import java.util.List;
import java.util.Random;
@RunWith(DevSdkIgnoreRunner.class)
@@ -532,6 +533,40 @@
assertEquals(512L + 4096L, stats.getTotalBytes());
}
+ @Test
+ public void testBuilder() {
+ final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 30, 40,
+ 4, 50, 5, 60);
+ final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(30, 15, 3,
+ 41, 7, 1, 0);
+ final NetworkStatsHistory.Entry entry3 = new NetworkStatsHistory.Entry(7, 301, 11,
+ 14, 31, 2, 80);
+
+ final NetworkStatsHistory statsEmpty = new NetworkStatsHistory
+ .Builder(HOUR_IN_MILLIS, 10).build();
+ assertEquals(0, statsEmpty.getEntries().size());
+ assertEquals(HOUR_IN_MILLIS, statsEmpty.getBucketDuration());
+
+ NetworkStatsHistory statsSingle = new NetworkStatsHistory
+ .Builder(HOUR_IN_MILLIS, 8)
+ .addEntry(entry1)
+ .build();
+ assertEquals(1, statsSingle.getEntries().size());
+ assertEquals(HOUR_IN_MILLIS, statsSingle.getBucketDuration());
+ assertEquals(entry1, statsSingle.getEntries().get(0));
+
+ NetworkStatsHistory statsMultiple = new NetworkStatsHistory
+ .Builder(SECOND_IN_MILLIS, 0)
+ .addEntry(entry1).addEntry(entry2).addEntry(entry3)
+ .build();
+ final List<NetworkStatsHistory.Entry> entries = statsMultiple.getEntries();
+ assertEquals(3, entries.size());
+ assertEquals(SECOND_IN_MILLIS, statsMultiple.getBucketDuration());
+ assertEquals(entry1, entries.get(0));
+ assertEquals(entry2, entries.get(1));
+ assertEquals(entry3, entries.get(2));
+ }
+
private static void assertIndexBeforeAfter(
NetworkStatsHistory stats, int before, int after, long time) {
assertEquals("unexpected before", before, stats.getIndexBefore(time));
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index c971da1..b0cc16c 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -37,6 +37,7 @@
import static android.net.NetworkStats.UID_ALL;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.os.Build;
@@ -53,8 +54,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Iterator;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@@ -1037,6 +1040,29 @@
assertEquals(secondEntry, stats.getValues(1, null));
}
+ @Test
+ public void testIterator() {
+ final NetworkStats emptyStats = new NetworkStats(0, 0);
+ final Iterator emptyIterator = emptyStats.iterator();
+ assertFalse(emptyIterator.hasNext());
+
+ final int numEntries = 10;
+ final ArrayList<NetworkStats.Entry> entries = new ArrayList<>();
+ final NetworkStats stats = new NetworkStats(TEST_START, 1);
+ for (int i = 0; i < numEntries; ++i) {
+ NetworkStats.Entry entry = new NetworkStats.Entry("test1", 10100, SET_DEFAULT,
+ TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+ i * 10L /* rxBytes */, i * 3L /* rxPackets */,
+ i * 15L /* txBytes */, i * 2L /* txPackets */, 0L /* operations */);
+ stats.insertEntry(entry);
+ entries.add(entry);
+ }
+
+ for (NetworkStats.Entry e : stats) {
+ assertEquals(e, entries.remove(0));
+ }
+ }
+
private static void assertContains(NetworkStats stats, String iface, int uid, int set,
int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
long txBytes, long txPackets, long operations) {
@@ -1057,22 +1083,22 @@
private static void assertValues(
NetworkStats.Entry entry, String iface, int uid, int set, int tag, int metered,
int roaming, int defaultNetwork) {
- assertEquals(iface, entry.iface);
- assertEquals(uid, entry.uid);
- assertEquals(set, entry.set);
- assertEquals(tag, entry.tag);
- assertEquals(metered, entry.metered);
- assertEquals(roaming, entry.roaming);
- assertEquals(defaultNetwork, entry.defaultNetwork);
+ assertEquals(iface, entry.getIface());
+ assertEquals(uid, entry.getUid());
+ assertEquals(set, entry.getSet());
+ assertEquals(tag, entry.getTag());
+ assertEquals(metered, entry.getMetered());
+ assertEquals(roaming, entry.getRoaming());
+ assertEquals(defaultNetwork, entry.getDefaultNetwork());
}
private static void assertValues(NetworkStats.Entry entry, long rxBytes, long rxPackets,
long txBytes, long txPackets, long operations) {
- assertEquals(rxBytes, entry.rxBytes);
- assertEquals(rxPackets, entry.rxPackets);
- assertEquals(txBytes, entry.txBytes);
- assertEquals(txPackets, entry.txPackets);
- assertEquals(operations, entry.operations);
+ assertEquals(rxBytes, entry.getRxBytes());
+ assertEquals(rxPackets, entry.getRxPackets());
+ assertEquals(txBytes, entry.getTxBytes());
+ assertEquals(txPackets, entry.getTxPackets());
+ assertEquals(operations, entry.getOperations());
}
}
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 572c1ef..453612f 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -16,30 +16,32 @@
package android.net
+import android.app.usage.NetworkStatsManager.NETWORK_TYPE_5G_NSA
import android.content.Context
import android.net.ConnectivityManager.TYPE_MOBILE
import android.net.ConnectivityManager.TYPE_WIFI
import android.net.NetworkIdentity.OEM_NONE
import android.net.NetworkIdentity.OEM_PAID
import android.net.NetworkIdentity.OEM_PRIVATE
-import android.net.NetworkIdentity.SUBTYPE_COMBINED
import android.net.NetworkIdentity.buildNetworkIdentity
import android.net.NetworkStats.DEFAULT_NETWORK_ALL
import android.net.NetworkStats.METERED_ALL
import android.net.NetworkStats.METERED_NO
import android.net.NetworkStats.METERED_YES
import android.net.NetworkStats.ROAMING_ALL
+import android.net.NetworkTemplate.MATCH_BLUETOOTH
+import android.net.NetworkTemplate.MATCH_CARRIER
+import android.net.NetworkTemplate.MATCH_ETHERNET
import android.net.NetworkTemplate.MATCH_MOBILE
import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
+import android.net.NetworkTemplate.MATCH_PROXY
import android.net.NetworkTemplate.MATCH_WIFI
import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
-import android.net.NetworkTemplate.NETWORK_TYPE_5G_NSA
import android.net.NetworkTemplate.NETWORK_TYPE_ALL
import android.net.NetworkTemplate.OEM_MANAGED_ALL
import android.net.NetworkTemplate.OEM_MANAGED_NO
import android.net.NetworkTemplate.OEM_MANAGED_YES
-import android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT
-import android.net.NetworkTemplate.WIFI_NETWORKID_ALL
+import android.net.NetworkTemplate.WIFI_NETWORK_KEY_ALL
import android.net.NetworkTemplate.buildTemplateCarrierMetered
import android.net.NetworkTemplate.buildTemplateMobileAll
import android.net.NetworkTemplate.buildTemplateMobileWildcard
@@ -47,17 +49,23 @@
import android.net.NetworkTemplate.buildTemplateWifi
import android.net.NetworkTemplate.buildTemplateWifiWildcard
import android.net.NetworkTemplate.normalize
+import android.net.wifi.WifiInfo
import android.os.Build
import android.telephony.TelephonyManager
+import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
import com.android.testutils.assertParcelSane
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
@@ -65,35 +73,38 @@
private const val TEST_IMSI1 = "imsi1"
private const val TEST_IMSI2 = "imsi2"
private const val TEST_IMSI3 = "imsi3"
-private const val TEST_SSID1 = "ssid1"
-private const val TEST_SSID2 = "ssid2"
+private const val TEST_WIFI_KEY1 = "wifiKey1"
+private const val TEST_WIFI_KEY2 = "wifiKey2"
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class NetworkTemplateTest {
private val mockContext = mock(Context::class.java)
+ private val mockWifiInfo = mock(WifiInfo::class.java)
private fun buildMobileNetworkState(subscriberId: String): NetworkStateSnapshot =
buildNetworkState(TYPE_MOBILE, subscriberId = subscriberId)
- private fun buildWifiNetworkState(subscriberId: String?, ssid: String?): NetworkStateSnapshot =
- buildNetworkState(TYPE_WIFI, subscriberId = subscriberId, ssid = ssid)
+ private fun buildWifiNetworkState(subscriberId: String?, wifiKey: String?):
+ NetworkStateSnapshot = buildNetworkState(TYPE_WIFI,
+ subscriberId = subscriberId, wifiKey = wifiKey)
private fun buildNetworkState(
type: Int,
subscriberId: String? = null,
- ssid: String? = null,
+ wifiKey: String? = null,
oemManaged: Int = OEM_NONE,
metered: Boolean = true
): NetworkStateSnapshot {
+ `when`(mockWifiInfo.getNetworkKey()).thenReturn(wifiKey)
val lp = LinkProperties()
val caps = NetworkCapabilities().apply {
setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !metered)
setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true)
- setSSID(ssid)
setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID,
(oemManaged and OEM_PAID) == OEM_PAID)
setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE,
(oemManaged and OEM_PRIVATE) == OEM_PRIVATE)
+ setTransportInfo(mockWifiInfo)
}
return NetworkStateSnapshot(mock(Network::class.java), caps, lp, subscriberId, type)
}
@@ -116,64 +127,65 @@
val identMobileImsi1 = buildNetworkIdentity(mockContext,
buildMobileNetworkState(TEST_IMSI1),
false, TelephonyManager.NETWORK_TYPE_UMTS)
- val identWifiImsiNullSsid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0)
- val identWifiImsi1Ssid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
+ val identWifiImsiNullKey1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(null, TEST_WIFI_KEY1), true, 0)
+ val identWifiImsi1Key1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY1), true, 0)
templateWifiWildcard.assertDoesNotMatch(identMobileImsi1)
- templateWifiWildcard.assertMatches(identWifiImsiNullSsid1)
- templateWifiWildcard.assertMatches(identWifiImsi1Ssid1)
+ templateWifiWildcard.assertMatches(identWifiImsiNullKey1)
+ templateWifiWildcard.assertMatches(identWifiImsi1Key1)
}
@Test
fun testWifiMatches() {
- val templateWifiSsid1 = buildTemplateWifi(TEST_SSID1)
- val templateWifiSsid1ImsiNull = buildTemplateWifi(TEST_SSID1, null)
- val templateWifiSsid1Imsi1 = buildTemplateWifi(TEST_SSID1, TEST_IMSI1)
- val templateWifiSsidAllImsi1 = buildTemplateWifi(WIFI_NETWORKID_ALL, TEST_IMSI1)
+ val templateWifiKey1 = buildTemplateWifi(TEST_WIFI_KEY1)
+ val templateWifiKey1ImsiNull = buildTemplateWifi(TEST_WIFI_KEY1, null)
+ val templateWifiKey1Imsi1 = buildTemplateWifi(TEST_WIFI_KEY1, TEST_IMSI1)
+ val templateWifiKeyAllImsi1 = buildTemplateWifi(WIFI_NETWORK_KEY_ALL, TEST_IMSI1)
val identMobile1 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI1),
false, TelephonyManager.NETWORK_TYPE_UMTS)
- val identWifiImsiNullSsid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0)
- val identWifiImsi1Ssid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
- val identWifiImsi2Ssid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_SSID1), true, 0)
- val identWifiImsi1Ssid2 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID2), true, 0)
+ val identWifiImsiNullKey1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(null, TEST_WIFI_KEY1), true, 0)
+ val identWifiImsi1Key1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY1), true, 0)
+ val identWifiImsi2Key1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_WIFI_KEY1), true, 0)
+ val identWifiImsi1Key2 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY2), true, 0)
- // Verify that template with SSID only matches any subscriberId and specific SSID.
- templateWifiSsid1.assertDoesNotMatch(identMobile1)
- templateWifiSsid1.assertMatches(identWifiImsiNullSsid1)
- templateWifiSsid1.assertMatches(identWifiImsi1Ssid1)
- templateWifiSsid1.assertMatches(identWifiImsi2Ssid1)
- templateWifiSsid1.assertDoesNotMatch(identWifiImsi1Ssid2)
+ // Verify that template with WiFi Network Key only matches any subscriberId and
+ // specific WiFi Network Key.
+ templateWifiKey1.assertDoesNotMatch(identMobile1)
+ templateWifiKey1.assertMatches(identWifiImsiNullKey1)
+ templateWifiKey1.assertMatches(identWifiImsi1Key1)
+ templateWifiKey1.assertMatches(identWifiImsi2Key1)
+ templateWifiKey1.assertDoesNotMatch(identWifiImsi1Key2)
- // Verify that template with SSID1 and null imsi matches any network with
- // SSID1 and null imsi.
- templateWifiSsid1ImsiNull.assertDoesNotMatch(identMobile1)
- templateWifiSsid1ImsiNull.assertMatches(identWifiImsiNullSsid1)
- templateWifiSsid1ImsiNull.assertDoesNotMatch(identWifiImsi1Ssid1)
- templateWifiSsid1ImsiNull.assertDoesNotMatch(identWifiImsi2Ssid1)
- templateWifiSsid1ImsiNull.assertDoesNotMatch(identWifiImsi1Ssid2)
+ // Verify that template with WiFi Network Key1 and null imsi matches any network with
+ // WiFi Network Key1 and null imsi.
+ templateWifiKey1ImsiNull.assertDoesNotMatch(identMobile1)
+ templateWifiKey1ImsiNull.assertMatches(identWifiImsiNullKey1)
+ templateWifiKey1ImsiNull.assertDoesNotMatch(identWifiImsi1Key1)
+ templateWifiKey1ImsiNull.assertDoesNotMatch(identWifiImsi2Key1)
+ templateWifiKey1ImsiNull.assertDoesNotMatch(identWifiImsi1Key2)
- // Verify that template with SSID1 and imsi1 matches any network with
- // SSID1 and imsi1.
- templateWifiSsid1Imsi1.assertDoesNotMatch(identMobile1)
- templateWifiSsid1Imsi1.assertDoesNotMatch(identWifiImsiNullSsid1)
- templateWifiSsid1Imsi1.assertMatches(identWifiImsi1Ssid1)
- templateWifiSsid1Imsi1.assertDoesNotMatch(identWifiImsi2Ssid1)
- templateWifiSsid1Imsi1.assertDoesNotMatch(identWifiImsi1Ssid2)
+ // Verify that template with WiFi Network Key1 and imsi1 matches any network with
+ // WiFi Network Key1 and imsi1.
+ templateWifiKey1Imsi1.assertDoesNotMatch(identMobile1)
+ templateWifiKey1Imsi1.assertDoesNotMatch(identWifiImsiNullKey1)
+ templateWifiKey1Imsi1.assertMatches(identWifiImsi1Key1)
+ templateWifiKey1Imsi1.assertDoesNotMatch(identWifiImsi2Key1)
+ templateWifiKey1Imsi1.assertDoesNotMatch(identWifiImsi1Key2)
- // Verify that template with SSID all and imsi1 matches any network with
- // any SSID and imsi1.
- templateWifiSsidAllImsi1.assertDoesNotMatch(identMobile1)
- templateWifiSsidAllImsi1.assertDoesNotMatch(identWifiImsiNullSsid1)
- templateWifiSsidAllImsi1.assertMatches(identWifiImsi1Ssid1)
- templateWifiSsidAllImsi1.assertDoesNotMatch(identWifiImsi2Ssid1)
- templateWifiSsidAllImsi1.assertMatches(identWifiImsi1Ssid2)
+ // Verify that template with WiFi Network Key all and imsi1 matches any network with
+ // any WiFi Network Key and imsi1.
+ templateWifiKeyAllImsi1.assertDoesNotMatch(identMobile1)
+ templateWifiKeyAllImsi1.assertDoesNotMatch(identWifiImsiNullKey1)
+ templateWifiKeyAllImsi1.assertMatches(identWifiImsi1Key1)
+ templateWifiKeyAllImsi1.assertDoesNotMatch(identWifiImsi2Key1)
+ templateWifiKeyAllImsi1.assertMatches(identWifiImsi1Key2)
}
@Test
@@ -182,7 +194,7 @@
val templateMobileImsi2WithRatType = buildTemplateMobileWithRatType(TEST_IMSI2,
TelephonyManager.NETWORK_TYPE_UMTS, METERED_YES)
- val mobileImsi1 = buildNetworkState(TYPE_MOBILE, TEST_IMSI1, null /* ssid */,
+ val mobileImsi1 = buildNetworkState(TYPE_MOBILE, TEST_IMSI1, null /* wifiKey */,
OEM_NONE, true /* metered */)
val identMobile1 = buildNetworkIdentity(mockContext, mobileImsi1,
false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
@@ -190,8 +202,8 @@
val identMobile2Umts = buildNetworkIdentity(mockContext, mobileImsi2,
false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
- val identWifiImsi1Ssid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
+ val identWifiImsi1Key1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY1), true, 0)
// Verify that the template matches type and the subscriberId.
templateMobileImsi1.assertMatches(identMobile1)
@@ -202,7 +214,7 @@
templateMobileImsi2WithRatType.assertDoesNotMatch(identMobile1)
// Verify that the different type does not match.
- templateMobileImsi1.assertDoesNotMatch(identWifiImsi1Ssid1)
+ templateMobileImsi1.assertDoesNotMatch(identWifiImsi1Key1)
}
@Test
@@ -219,12 +231,12 @@
templateMobileWildcard.assertMatches(identMobile1)
templateMobileNullImsiWithRatType.assertMatches(identMobile1)
- val identWifiImsi1Ssid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
+ val identWifiImsi1Key1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY1), true, 0)
// Verify that the different type does not match.
- templateMobileWildcard.assertDoesNotMatch(identWifiImsi1Ssid1)
- templateMobileNullImsiWithRatType.assertDoesNotMatch(identWifiImsi1Ssid1)
+ templateMobileWildcard.assertDoesNotMatch(identWifiImsi1Key1)
+ templateMobileNullImsiWithRatType.assertDoesNotMatch(identWifiImsi1Key1)
}
@Test
@@ -232,13 +244,14 @@
val templateCarrierImsi1Metered = buildTemplateCarrierMetered(TEST_IMSI1)
val mobileImsi1 = buildMobileNetworkState(TEST_IMSI1)
- val mobileImsi1Unmetered = buildNetworkState(TYPE_MOBILE, TEST_IMSI1, null /* ssid */,
- OEM_NONE, false /* metered */)
+ val mobileImsi1Unmetered = buildNetworkState(TYPE_MOBILE, TEST_IMSI1,
+ null /* wifiKey */, OEM_NONE, false /* metered */)
val mobileImsi2 = buildMobileNetworkState(TEST_IMSI2)
- val wifiSsid1 = buildWifiNetworkState(null /* subscriberId */, TEST_SSID1)
- val wifiImsi1Ssid1 = buildWifiNetworkState(TEST_IMSI1, TEST_SSID1)
- val wifiImsi1Ssid1Unmetered = buildNetworkState(TYPE_WIFI, TEST_IMSI1, TEST_SSID1,
- OEM_NONE, false /* metered */)
+ val wifiKey1 = buildWifiNetworkState(null /* subscriberId */,
+ TEST_WIFI_KEY1)
+ val wifiImsi1Key1 = buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY1)
+ val wifiImsi1Key1Unmetered = buildNetworkState(TYPE_WIFI, TEST_IMSI1,
+ TEST_WIFI_KEY1, OEM_NONE, false /* metered */)
val identMobileImsi1Metered = buildNetworkIdentity(mockContext,
mobileImsi1, false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
@@ -247,17 +260,17 @@
TelephonyManager.NETWORK_TYPE_UMTS)
val identMobileImsi2Metered = buildNetworkIdentity(mockContext,
mobileImsi2, false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
- val identWifiSsid1Metered = buildNetworkIdentity(
- mockContext, wifiSsid1, true /* defaultNetwork */, 0 /* subType */)
+ val identWifiKey1Metered = buildNetworkIdentity(
+ mockContext, wifiKey1, true /* defaultNetwork */, 0 /* subType */)
val identCarrierWifiImsi1Metered = buildNetworkIdentity(
- mockContext, wifiImsi1Ssid1, true /* defaultNetwork */, 0 /* subType */)
+ mockContext, wifiImsi1Key1, true /* defaultNetwork */, 0 /* subType */)
val identCarrierWifiImsi1NonMetered = buildNetworkIdentity(mockContext,
- wifiImsi1Ssid1Unmetered, true /* defaultNetwork */, 0 /* subType */)
+ wifiImsi1Key1Unmetered, true /* defaultNetwork */, 0 /* subType */)
templateCarrierImsi1Metered.assertMatches(identMobileImsi1Metered)
templateCarrierImsi1Metered.assertDoesNotMatch(identMobileImsi1Unmetered)
templateCarrierImsi1Metered.assertDoesNotMatch(identMobileImsi2Metered)
- templateCarrierImsi1Metered.assertDoesNotMatch(identWifiSsid1Metered)
+ templateCarrierImsi1Metered.assertDoesNotMatch(identWifiKey1Metered)
templateCarrierImsi1Metered.assertMatches(identCarrierWifiImsi1Metered)
templateCarrierImsi1Metered.assertDoesNotMatch(identCarrierWifiImsi1NonMetered)
}
@@ -267,9 +280,9 @@
fun testRatTypeGroupMatches() {
val stateMobileImsi1Metered = buildMobileNetworkState(TEST_IMSI1)
val stateMobileImsi1NonMetered = buildNetworkState(TYPE_MOBILE, TEST_IMSI1,
- null /* ssid */, OEM_NONE, false /* metered */)
+ null /* wifiKey */, OEM_NONE, false /* metered */)
val stateMobileImsi2NonMetered = buildNetworkState(TYPE_MOBILE, TEST_IMSI2,
- null /* ssid */, OEM_NONE, false /* metered */)
+ null /* wifiKey */, OEM_NONE, false /* metered */)
// Build UMTS template that matches mobile identities with RAT in the same
// group with any IMSI. See {@link NetworkTemplate#getCollapsedRatType}.
@@ -299,11 +312,11 @@
val identLteMetered = buildNetworkIdentity(
mockContext, stateMobileImsi1Metered, false, TelephonyManager.NETWORK_TYPE_LTE)
val identCombinedMetered = buildNetworkIdentity(
- mockContext, stateMobileImsi1Metered, false, SUBTYPE_COMBINED)
+ mockContext, stateMobileImsi1Metered, false, NetworkTemplate.NETWORK_TYPE_ALL)
val identImsi2UmtsMetered = buildNetworkIdentity(mockContext,
buildMobileNetworkState(TEST_IMSI2), false, TelephonyManager.NETWORK_TYPE_UMTS)
val identWifi = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0)
+ mockContext, buildWifiNetworkState(null, TEST_WIFI_KEY1), true, 0)
val identUmtsNonMetered = buildNetworkIdentity(
mockContext, stateMobileImsi1NonMetered, false, TelephonyManager.NETWORK_TYPE_UMTS)
@@ -313,7 +326,7 @@
val identLteNonMetered = buildNetworkIdentity(
mockContext, stateMobileImsi1NonMetered, false, TelephonyManager.NETWORK_TYPE_LTE)
val identCombinedNonMetered = buildNetworkIdentity(
- mockContext, stateMobileImsi1NonMetered, false, SUBTYPE_COMBINED)
+ mockContext, stateMobileImsi1NonMetered, false, NetworkTemplate.NETWORK_TYPE_ALL)
val identImsi2UmtsNonMetered = buildNetworkIdentity(mockContext,
stateMobileImsi2NonMetered, false, TelephonyManager.NETWORK_TYPE_UMTS)
@@ -391,15 +404,16 @@
@Test
fun testParcelUnparcel() {
- val templateMobile = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1, null, null, METERED_ALL,
- ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_LTE,
+ val templateMobile = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1, null,
+ arrayOf<String>(), METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+ TelephonyManager.NETWORK_TYPE_LTE, OEM_MANAGED_ALL,
+ SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ val templateWifi = NetworkTemplate(MATCH_WIFI, null, null,
+ arrayOf(TEST_WIFI_KEY1), METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, 0,
OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
- val templateWifi = NetworkTemplate(MATCH_WIFI, null, null, TEST_SSID1, METERED_ALL,
- ROAMING_ALL, DEFAULT_NETWORK_ALL, 0, OEM_MANAGED_ALL,
- SUBSCRIBER_ID_MATCH_RULE_EXACT)
- val templateOem = NetworkTemplate(MATCH_MOBILE, null, null, null, METERED_ALL,
- ROAMING_ALL, DEFAULT_NETWORK_ALL, 0, OEM_MANAGED_YES,
- SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ val templateOem = NetworkTemplate(MATCH_MOBILE, null, null,
+ arrayOf<String>(), METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, 0,
+ OEM_MANAGED_YES, SUBSCRIBER_ID_MATCH_RULE_EXACT)
assertParcelSane(templateMobile, 10)
assertParcelSane(templateWifi, 10)
assertParcelSane(templateOem, 10)
@@ -437,39 +451,43 @@
* @param subscriberId To be populated with {@code TEST_IMSI*} only if networkType is
* {@code TYPE_MOBILE}. May be left as null when matchType is
* {@link NetworkTemplate.MATCH_MOBILE_WILDCARD}.
- * @param templateSsid Top be populated with {@code TEST_SSID*} only if networkType is
+ * @param templateWifiKey Top be populated with {@code TEST_WIFI_KEY*} only if networkType is
* {@code TYPE_WIFI}. May be left as null when matchType is
* {@link NetworkTemplate.MATCH_WIFI_WILDCARD}.
- * @param identSsid If networkType is {@code TYPE_WIFI}, this value must *NOT* be null. Provide
- * one of {@code TEST_SSID*}.
+ * @param identWifiKey If networkType is {@code TYPE_WIFI}, this value must *NOT* be null. Provide
+ * one of {@code TEST_WIFI_KEY*}.
*/
private fun matchOemManagedIdent(
networkType: Int,
matchType: Int,
subscriberId: String? = null,
- templateSsid: String? = null,
- identSsid: String? = null
+ templateWifiKey: String? = null,
+ identWifiKey: String? = null
) {
val oemManagedStates = arrayOf(OEM_NONE, OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE)
val matchSubscriberIds = arrayOf(subscriberId)
+ val matchWifiNetworkKeys = arrayOf(templateWifiKey)
val templateOemYes = NetworkTemplate(matchType, subscriberId, matchSubscriberIds,
- templateSsid, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_YES, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ matchWifiNetworkKeys, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_YES,
+ SUBSCRIBER_ID_MATCH_RULE_EXACT)
val templateOemAll = NetworkTemplate(matchType, subscriberId, matchSubscriberIds,
- templateSsid, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ matchWifiNetworkKeys, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+ SUBSCRIBER_ID_MATCH_RULE_EXACT)
for (identityOemManagedState in oemManagedStates) {
val ident = buildNetworkIdentity(mockContext, buildNetworkState(networkType,
- subscriberId, identSsid, identityOemManagedState), /*defaultNetwork=*/false,
- /*subType=*/0)
+ subscriberId, identWifiKey, identityOemManagedState),
+ /*defaultNetwork=*/false, /*subType=*/0)
// Create a template with each OEM managed type and match it against the NetworkIdentity
for (templateOemManagedState in oemManagedStates) {
val template = NetworkTemplate(matchType, subscriberId, matchSubscriberIds,
- templateSsid, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
- NETWORK_TYPE_ALL, templateOemManagedState, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ matchWifiNetworkKeys, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, templateOemManagedState,
+ SUBSCRIBER_ID_MATCH_RULE_EXACT)
if (identityOemManagedState == templateOemManagedState) {
template.assertMatches(ident)
} else {
@@ -491,9 +509,10 @@
fun testOemManagedMatchesIdent() {
matchOemManagedIdent(TYPE_MOBILE, MATCH_MOBILE, subscriberId = TEST_IMSI1)
matchOemManagedIdent(TYPE_MOBILE, MATCH_MOBILE_WILDCARD)
- matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI, templateSsid = TEST_SSID1,
- identSsid = TEST_SSID1)
- matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI_WILDCARD, identSsid = TEST_SSID1)
+ matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI, templateWifiKey = TEST_WIFI_KEY1,
+ identWifiKey = TEST_WIFI_KEY1)
+ matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI_WILDCARD,
+ identWifiKey = TEST_WIFI_KEY1)
}
@Test
@@ -508,12 +527,12 @@
val identMobileImsi3 = buildNetworkIdentity(mockContext,
buildMobileNetworkState(TEST_IMSI3), false /* defaultNetwork */,
TelephonyManager.NETWORK_TYPE_UMTS)
- val identWifiImsi1Ssid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
- val identWifiImsi2Ssid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_SSID1), true, 0)
- val identWifiImsi3Ssid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI3, TEST_SSID1), true, 0)
+ val identWifiImsi1Key1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY1), true, 0)
+ val identWifiImsi2Key1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_WIFI_KEY1), true, 0)
+ val identWifiImsi3WifiKey1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI3, TEST_WIFI_KEY1), true, 0)
normalize(buildTemplateMobileAll(TEST_IMSI1), mergedImsiList).also {
it.assertMatches(identMobileImsi1)
@@ -525,10 +544,10 @@
it.assertMatches(identMobileImsi2)
it.assertDoesNotMatch(identMobileImsi3)
}
- normalize(buildTemplateWifi(TEST_SSID1, TEST_IMSI1), mergedImsiList).also {
- it.assertMatches(identWifiImsi1Ssid1)
- it.assertMatches(identWifiImsi2Ssid1)
- it.assertDoesNotMatch(identWifiImsi3Ssid1)
+ normalize(buildTemplateWifi(TEST_WIFI_KEY1, TEST_IMSI1), mergedImsiList).also {
+ it.assertMatches(identWifiImsi1Key1)
+ it.assertMatches(identWifiImsi2Key1)
+ it.assertDoesNotMatch(identWifiImsi3WifiKey1)
}
normalize(buildTemplateMobileWildcard(), mergedImsiList).also {
it.assertMatches(identMobileImsi1)
@@ -536,4 +555,140 @@
it.assertMatches(identMobileImsi3)
}
}
+
+ @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+ @Test
+ fun testBuilderMatchRules() {
+ // Verify unknown match rules cannot construct templates.
+ listOf(Integer.MIN_VALUE, -1, Integer.MAX_VALUE).forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(it).build()
+ }
+ }
+
+ // Verify hidden match rules cannot construct templates.
+ listOf(MATCH_WIFI_WILDCARD, MATCH_MOBILE_WILDCARD, MATCH_PROXY).forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(it).build()
+ }
+ }
+
+ // Verify template which matches metered cellular and carrier networks with
+ // the given IMSI. See buildTemplateMobileAll and buildTemplateCarrierMetered.
+ listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
+ NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
+ .setMeteredness(METERED_YES).build().let {
+ val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+
+ // Verify carrier template cannot be created without IMSI.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(MATCH_CARRIER).build()
+ }
+
+ // Verify template which matches metered cellular networks,
+ // regardless of IMSI. See buildTemplateMobileWildcard.
+ NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_MOBILE_WILDCARD, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf<String>(),
+ METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches metered cellular networks and ratType.
+ // See NetworkTemplate#buildTemplateMobileWithRatType.
+ NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(TEST_IMSI1))
+ .setMeteredness(METERED_YES).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
+ .build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_UMTS,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches all wifi networks,
+ // regardless of Wifi Network Key. See buildTemplateWifiWildcard and buildTemplateWifi.
+ NetworkTemplate.Builder(MATCH_WIFI).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf<String>(),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches wifi networks with the given Wifi Network Key.
+ // See buildTemplateWifi(wifiNetworkKey).
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf(TEST_WIFI_KEY1),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches all wifi networks with the
+ // given Wifi Network Key, and IMSI. See buildTemplateWifi(wifiNetworkKey, subscriberId).
+ NetworkTemplate.Builder(MATCH_WIFI).setSubscriberIds(setOf(TEST_IMSI1))
+ .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf(TEST_WIFI_KEY1),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches ethernet and bluetooth networks.
+ // See buildTemplateEthernet and buildTemplateBluetooth.
+ listOf(MATCH_ETHERNET, MATCH_BLUETOOTH).forEach { matchRule ->
+ NetworkTemplate.Builder(matchRule).build().let {
+ val expectedTemplate = NetworkTemplate(matchRule, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf<String>(),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+ }
+
+ @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+ @Test
+ fun testBuilderWifiNetworkKeys() {
+ // Verify template builder which generates same template with the given different
+ // sequence keys.
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
+ setOf(TEST_WIFI_KEY1, TEST_WIFI_KEY2)).build().let {
+ val expectedTemplate = NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
+ setOf(TEST_WIFI_KEY2, TEST_WIFI_KEY1)).build()
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches non-wifi networks with the given key is invalid.
+ listOf(MATCH_MOBILE, MATCH_CARRIER, MATCH_ETHERNET, MATCH_BLUETOOTH, -1,
+ Integer.MAX_VALUE).forEach { matchRule ->
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(matchRule).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
+ }
+ }
+
+ // Verify template which matches wifi networks with the given null key is invalid.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(null)).build()
+ }
+
+ // Verify template which matches wifi wildcard with the given empty key set.
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf<String>()).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
+ arrayOf<String>() /*subscriberIds*/, arrayOf<String>(),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+ }
}
diff --git a/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
new file mode 100644
index 0000000..e4943ea
--- /dev/null
+++ b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.netstats
+
+import android.net.NetworkStatsCollection
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import com.android.frameworks.tests.net.R
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+import java.io.DataInputStream
+import java.net.ProtocolException
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
+
+private const val BUCKET_DURATION_MS = 2 * 60 * 60 * 1000L
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+class NetworkStatsDataMigrationUtilsTest {
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testReadPlatformCollection() {
+ // Verify the method throws for wrong file version.
+ assertFailsWith<ProtocolException> {
+ NetworkStatsDataMigrationUtils.readPlatformCollection(
+ NetworkStatsCollection.Builder(BUCKET_DURATION_MS),
+ getInputStreamForResource(R.raw.netstats_uid_v4))
+ }
+
+ val builder = NetworkStatsCollection.Builder(BUCKET_DURATION_MS)
+ NetworkStatsDataMigrationUtils.readPlatformCollection(builder,
+ getInputStreamForResource(R.raw.netstats_uid_v16))
+ // The values are obtained by dumping from NetworkStatsCollection that
+ // read by the logic inside the service.
+ assertValues(builder.build(), 55, 1814302L, 21050L, 31001636L, 26152L)
+ }
+
+ @Test
+ fun testMaybeReadLegacyUid() {
+ val builder = NetworkStatsCollection.Builder(BUCKET_DURATION_MS)
+ NetworkStatsDataMigrationUtils.readLegacyUid(builder,
+ getInputStreamForResource(R.raw.netstats_uid_v4), false /* taggedData */)
+ assertValues(builder.build(), 223, 106245210L, 710722L, 1130647496L, 1103989L)
+ }
+
+ private fun assertValues(
+ collection: NetworkStatsCollection,
+ expectedSize: Int,
+ expectedTxBytes: Long,
+ expectedTxPackets: Long,
+ expectedRxBytes: Long,
+ expectedRxPackets: Long
+ ) {
+ var txBytes = 0L
+ var txPackets = 0L
+ var rxBytes = 0L
+ var rxPackets = 0L
+ val entries = collection.entries
+
+ for (history in entries.values) {
+ for (historyEntry in history.entries) {
+ txBytes += historyEntry.txBytes
+ txPackets += historyEntry.txPackets
+ rxBytes += historyEntry.rxBytes
+ rxPackets += historyEntry.rxPackets
+ }
+ }
+ if (expectedSize != entries.size ||
+ expectedTxBytes != txBytes ||
+ expectedTxPackets != txPackets ||
+ expectedRxBytes != rxBytes ||
+ expectedRxPackets != rxPackets) {
+ fail("expected size=$expectedSize" +
+ "txb=$expectedTxBytes txp=$expectedTxPackets " +
+ "rxb=$expectedRxBytes rxp=$expectedRxPackets bus was " +
+ "size=${entries.size} txb=$txBytes txp=$txPackets " +
+ "rxb=$rxBytes rxp=$rxPackets")
+ }
+ assertEquals(txBytes + rxBytes, collection.totalBytes)
+ }
+
+ private fun getInputStreamForResource(resourceId: Int): DataInputStream {
+ return DataInputStream(InstrumentationRegistry.getContext()
+ .getResources().openRawResource(resourceId))
+ }
+}
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
index a945a1f..960a9f1 100644
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
@@ -16,6 +16,7 @@
package com.android.internal.net;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.testutils.ParcelUtils.assertParcelSane;
import static org.junit.Assert.assertEquals;
@@ -48,6 +49,7 @@
private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
+ private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
@Test
public void testDefaults() throws Exception {
@@ -126,7 +128,12 @@
@Test
public void testParcelUnparcel() {
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
+ if (isAtLeastT()) {
+ // excludeLocalRoutes is added in T.
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 24);
+ } else {
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
+ }
}
@Test
@@ -166,7 +173,8 @@
final String tooFewValues =
getEncodedDecodedIkev2ProfileMissingValues(
ENCODED_INDEX_AUTH_PARAMS_INLINE,
- ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */);
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE /* missingIndices */);
assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
}
@@ -183,6 +191,17 @@
}
@Test
+ public void testEncodeDecodeMissingExcludeLocalRoutes() {
+ final String tooFewValues =
+ getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE /* missingIndices */);
+
+ // Verify decoding without isRestrictedToTestNetworks defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.excludeLocalRoutes);
+ }
+
+ @Test
public void testEncodeDecodeLoginsNotSaved() {
final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
profile.saveLogin = false;
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 044ff02..652aee9 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -51,6 +51,7 @@
import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
@@ -81,6 +82,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMTEL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -99,6 +101,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VSIM;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -249,6 +252,7 @@
import android.net.NetworkTestResultParcelable;
import android.net.OemNetworkPreferences;
import android.net.PacProxyManager;
+import android.net.ProfileNetworkPreference;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.QosCallbackException;
@@ -335,6 +339,7 @@
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
+import com.android.server.connectivity.UidRangeUtils;
import com.android.server.connectivity.Vpn;
import com.android.server.connectivity.VpnProfileStore;
import com.android.server.net.NetworkPinner;
@@ -347,6 +352,7 @@
import com.android.testutils.TestableNetworkOfferCallback;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -440,6 +446,10 @@
private static final int TEST_WORK_PROFILE_USER_ID = 2;
private static final int TEST_WORK_PROFILE_APP_UID =
UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID);
+ private static final int TEST_APP_ID_2 = 104;
+ private static final int TEST_WORK_PROFILE_APP_UID_2 =
+ UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID_2);
+
private static final String CLAT_PREFIX = "v4-";
private static final String MOBILE_IFNAME = "test_rmnet_data0";
private static final String CLAT_MOBILE_IFNAME = CLAT_PREFIX + MOBILE_IFNAME;
@@ -489,6 +499,8 @@
private TestNetworkCallback mSystemDefaultNetworkCallback;
private TestNetworkCallback mProfileDefaultNetworkCallback;
private TestNetworkCallback mTestPackageDefaultNetworkCallback;
+ private TestNetworkCallback mProfileDefaultNetworkCallbackAsAppUid2;
+ private TestNetworkCallback mTestPackageDefaultNetworkCallback2;
// State variables required to emulate NetworkPolicyManagerService behaviour.
private int mBlockedReasons = BLOCKED_REASON_NONE;
@@ -3385,12 +3397,12 @@
private NativeNetworkConfig nativeNetworkConfigPhysical(int netId, int permission) {
return new NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission,
- /*secure=*/ false, VpnManager.TYPE_VPN_NONE);
+ /*secure=*/ false, VpnManager.TYPE_VPN_NONE, /*excludeLocalRoutes=*/ false);
}
private NativeNetworkConfig nativeNetworkConfigVpn(int netId, boolean secure, int vpnType) {
return new NativeNetworkConfig(netId, NativeNetworkType.VIRTUAL, INetd.PERMISSION_NONE,
- secure, vpnType);
+ secure, vpnType, /*excludeLocalRoutes=*/ false);
}
@Test
@@ -3586,7 +3598,7 @@
|| capability == NET_CAPABILITY_IA || capability == NET_CAPABILITY_IMS
|| capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP
|| capability == NET_CAPABILITY_VSIM || capability == NET_CAPABILITY_BIP
- || capability == NET_CAPABILITY_ENTERPRISE) {
+ || capability == NET_CAPABILITY_ENTERPRISE || capability == NET_CAPABILITY_MMTEL) {
assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
} else {
assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
@@ -3714,6 +3726,7 @@
tryNetworkFactoryRequests(NET_CAPABILITY_WIFI_P2P);
tryNetworkFactoryRequests(NET_CAPABILITY_IA);
tryNetworkFactoryRequests(NET_CAPABILITY_RCS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_MMTEL);
tryNetworkFactoryRequests(NET_CAPABILITY_XCAP);
tryNetworkFactoryRequests(NET_CAPABILITY_ENTERPRISE);
tryNetworkFactoryRequests(NET_CAPABILITY_EIMS);
@@ -12243,6 +12256,8 @@
private void registerDefaultNetworkCallbacks() {
if (mSystemDefaultNetworkCallback != null || mDefaultNetworkCallback != null
|| mProfileDefaultNetworkCallback != null
+ || mProfileDefaultNetworkCallbackAsAppUid2 != null
+ || mTestPackageDefaultNetworkCallback2 != null
|| mTestPackageDefaultNetworkCallback != null) {
throw new IllegalStateException("Default network callbacks already registered");
}
@@ -12253,12 +12268,18 @@
mDefaultNetworkCallback = new TestNetworkCallback();
mProfileDefaultNetworkCallback = new TestNetworkCallback();
mTestPackageDefaultNetworkCallback = new TestNetworkCallback();
+ mProfileDefaultNetworkCallbackAsAppUid2 = new TestNetworkCallback();
+ mTestPackageDefaultNetworkCallback2 = new TestNetworkCallback();
mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback,
new Handler(ConnectivityThread.getInstanceLooper()));
mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback);
registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback,
TEST_WORK_PROFILE_APP_UID);
registerDefaultNetworkCallbackAsUid(mTestPackageDefaultNetworkCallback, TEST_PACKAGE_UID);
+ registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallbackAsAppUid2,
+ TEST_WORK_PROFILE_APP_UID_2);
+ registerDefaultNetworkCallbackAsUid(mTestPackageDefaultNetworkCallback2,
+ TEST_PACKAGE_UID2);
// TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well.
mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
}
@@ -12276,6 +12297,12 @@
if (null != mTestPackageDefaultNetworkCallback) {
mCm.unregisterNetworkCallback(mTestPackageDefaultNetworkCallback);
}
+ if (null != mProfileDefaultNetworkCallbackAsAppUid2) {
+ mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallbackAsAppUid2);
+ }
+ if (null != mTestPackageDefaultNetworkCallback2) {
+ mCm.unregisterNetworkCallback(mTestPackageDefaultNetworkCallback2);
+ }
}
private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
@@ -13704,10 +13731,34 @@
}
private UidRangeParcel[] uidRangeFor(final UserHandle handle) {
- UidRange range = UidRange.createForUser(handle);
+ final UidRange range = UidRange.createForUser(handle);
return new UidRangeParcel[] { new UidRangeParcel(range.start, range.stop) };
}
+ private UidRangeParcel[] uidRangeFor(final UserHandle handle,
+ ProfileNetworkPreference profileNetworkPreference) {
+ final Set<UidRange> uidRangeSet;
+ UidRange range = UidRange.createForUser(handle);
+ if (profileNetworkPreference.getIncludedUids().size() != 0) {
+ uidRangeSet = UidRangeUtils.convertListToUidRange(
+ profileNetworkPreference.getIncludedUids());
+ } else if (profileNetworkPreference.getExcludedUids().size() != 0) {
+ uidRangeSet = UidRangeUtils.removeRangeSetFromUidRange(
+ range, UidRangeUtils.convertListToUidRange(
+ profileNetworkPreference.getExcludedUids()));
+ } else {
+ uidRangeSet = new ArraySet<>();
+ uidRangeSet.add(range);
+ }
+ UidRangeParcel[] uidRangeParcels = new UidRangeParcel[uidRangeSet.size()];
+ int i = 0;
+ for (UidRange range1 : uidRangeSet) {
+ uidRangeParcels[i] = new UidRangeParcel(range1.start, range1.stop);
+ i++;
+ }
+ return uidRangeParcels;
+ }
+
private static class TestOnCompleteListener implements Runnable {
final class OnComplete {}
final ArrayTrackRecord<OnComplete>.ReadHead mHistory =
@@ -13730,6 +13781,14 @@
return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc);
}
+ private TestNetworkAgentWrapper makeEnterpriseNetworkAgent(int enterpriseId) throws Exception {
+ final NetworkCapabilities workNc = new NetworkCapabilities();
+ workNc.addCapability(NET_CAPABILITY_ENTERPRISE);
+ workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ workNc.addEnterpriseId(enterpriseId);
+ return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc);
+ }
+
private TestNetworkCallback mEnterpriseCallback;
private UserHandle setupEnterpriseNetwork() {
final UserHandle userHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
@@ -13753,72 +13812,116 @@
}
/**
- * Make sure per-profile networking preference behaves as expected when the enterprise network
- * goes up and down while the preference is active. Make sure they behave as expected whether
- * there is a general default network or not.
+ * Make sure per profile network preferences behave as expected for a given
+ * profile network preference.
*/
- @Test
- public void testPreferenceForUserNetworkUpDown() throws Exception {
+ public void testPreferenceForUserNetworkUpDownForGivenPreference(
+ ProfileNetworkPreference profileNetworkPreference,
+ boolean connectWorkProfileAgentAhead,
+ UserHandle testHandle,
+ TestNetworkCallback profileDefaultNetworkCallback,
+ TestNetworkCallback disAllowProfileDefaultNetworkCallback) throws Exception {
final InOrder inOrder = inOrder(mMockNetd);
- final UserHandle testHandle = setupEnterpriseNetwork();
- registerDefaultNetworkCallbacks();
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
- mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ profileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(
+ mCellNetworkAgent);
+ }
inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+ final TestNetworkAgentWrapper workAgent =
+ makeEnterpriseNetworkAgent(profileNetworkPreference.getPreferenceEnterpriseId());
+ if (connectWorkProfileAgentAhead) {
+ workAgent.connect(false);
+ }
final TestOnCompleteListener listener = new TestOnCompleteListener();
- mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ mCm.setProfileNetworkPreferences(testHandle, List.of(profileNetworkPreference),
r -> r.run(), listener);
listener.expectOnComplete();
-
- // Setting a network preference for this user will create a new set of routing rules for
- // the UID range that corresponds to this user, so as to define the default network
- // for these apps separately. This is true because the multi-layer request relevant to
- // this UID range contains a TRACK_DEFAULT, so the range will be moved through UID-specific
- // rules to the correct network – in this case the system default network. The case where
- // the default network for the profile happens to be the same as the system default
- // is not handled specially, the rules are always active as long as a preference is set.
- inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
- mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle),
- PREFERENCE_ORDER_PROFILE));
+ boolean allowFallback = true;
+ if (profileNetworkPreference.getPreference()
+ == PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK) {
+ allowFallback = false;
+ }
+ if (allowFallback && !connectWorkProfileAgentAhead) {
+ // Setting a network preference for this user will create a new set of routing rules for
+ // the UID range that corresponds to this user, inorder to define the default network
+ // for these apps separately. This is true because the multi-layer request relevant to
+ // this UID range contains a TRACK_DEFAULT, so the range will be moved through
+ // UID-specific rules to the correct network – in this case the system default network.
+ // The case where the default network for the profile happens to be the same as the
+ // system default is not handled specially, the rules are always active as long as
+ // a preference is set.
+ inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreference),
+ PREFERENCE_ORDER_PROFILE));
+ }
// The enterprise network is not ready yet.
- assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
- mProfileDefaultNetworkCallback);
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ if (allowFallback && !connectWorkProfileAgentAhead) {
+ assertNoCallbacks(profileDefaultNetworkCallback);
+ } else if (!connectWorkProfileAgentAhead) {
+ profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
+ }
+ }
- final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
- workAgent.connect(false);
+ if (!connectWorkProfileAgentAhead) {
+ workAgent.connect(false);
+ }
- mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent);
+ profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent);
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ disAllowProfileDefaultNetworkCallback.assertNoCallback();
+ }
mSystemDefaultNetworkCallback.assertNoCallback();
mDefaultNetworkCallback.assertNoCallback();
inOrder.verify(mMockNetd).networkCreate(
nativeNetworkConfigPhysical(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM));
inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
- workAgent.getNetwork().netId, uidRangeFor(testHandle), PREFERENCE_ORDER_PROFILE));
- inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
- mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle),
+ workAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreference),
PREFERENCE_ORDER_PROFILE));
+ if (allowFallback && !connectWorkProfileAgentAhead) {
+ inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreference),
+ PREFERENCE_ORDER_PROFILE));
+ }
+
// Make sure changes to the work agent send callbacks to the app in the work profile, but
// not to the other apps.
workAgent.setNetworkValid(true /* isStrictMode */);
workAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
- mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent,
+ profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent,
nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)
- && nc.hasCapability(NET_CAPABILITY_ENTERPRISE));
+ && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
+ && nc.hasEnterpriseId(
+ profileNetworkPreference.getPreferenceEnterpriseId())
+ && nc.getEnterpriseIds().length == 1);
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
+ }
assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
- mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc ->
+ profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc ->
nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
+ }
assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
// Conversely, change a capability on the system-wide default network and make sure
@@ -13828,7 +13931,11 @@
nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
- mProfileDefaultNetworkCallback.assertNoCallback();
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ disAllowProfileDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
+ nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+ }
+ profileDefaultNetworkCallback.assertNoCallback();
// Disconnect and reconnect the system-wide default network and make sure that the
// apps on this network see the appropriate callbacks, and the app on the work profile
@@ -13836,32 +13943,55 @@
mCellNetworkAgent.disconnect();
mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
- mProfileDefaultNetworkCallback.assertNoCallback();
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ disAllowProfileDefaultNetworkCallback.expectCallback(
+ CallbackEntry.LOST, mCellNetworkAgent);
+ }
+ profileDefaultNetworkCallback.assertNoCallback();
inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId);
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
- mProfileDefaultNetworkCallback.assertNoCallback();
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(
+ mCellNetworkAgent);
+
+ }
+ profileDefaultNetworkCallback.assertNoCallback();
inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
// When the agent disconnects, test that the app on the work profile falls back to the
// default network.
workAgent.disconnect();
- mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent);
- mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent);
+ if (allowFallback) {
+ profileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
+ }
+ }
assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
- inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
- mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle),
- PREFERENCE_ORDER_PROFILE));
+ if (allowFallback) {
+ inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreference),
+ PREFERENCE_ORDER_PROFILE));
+ }
inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId);
mCellNetworkAgent.disconnect();
mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
- mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ disAllowProfileDefaultNetworkCallback.expectCallback(
+ CallbackEntry.LOST, mCellNetworkAgent);
+ }
+ if (allowFallback) {
+ profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ }
// Waiting for the handler to be idle before checking for networkDestroy is necessary
// here because ConnectivityService calls onLost before the network is fully torn down.
@@ -13871,38 +14001,321 @@
// If the control comes here, callbacks seem to behave correctly in the presence of
// a default network when the enterprise network goes up and down. Now, make sure they
// also behave correctly in the absence of a system-wide default network.
- final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent();
+ final TestNetworkAgentWrapper workAgent2 =
+ makeEnterpriseNetworkAgent(profileNetworkPreference.getPreferenceEnterpriseId());
workAgent2.connect(false);
- mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2);
+ profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2);
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
+ }
assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));
inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
- workAgent2.getNetwork().netId, uidRangeFor(testHandle), PREFERENCE_ORDER_PROFILE));
+ workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreference), PREFERENCE_ORDER_PROFILE));
workAgent2.setNetworkValid(true /* isStrictMode */);
workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid());
- mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2,
+ profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2,
nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
- && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ && nc.hasEnterpriseId(
+ profileNetworkPreference.getPreferenceEnterpriseId())
+ && nc.getEnterpriseIds().length == 1);
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
+ }
assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
inOrder.verify(mMockNetd, never()).networkAddUidRangesParcel(any());
- // When the agent disconnects, test that the app on the work profile falls back to the
+ // When the agent disconnects, test that the app on the work profile fall back to the
// default network.
workAgent2.disconnect();
- mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2);
+ profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2);
+ if (disAllowProfileDefaultNetworkCallback != null) {
+ assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
+ }
assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
inOrder.verify(mMockNetd).networkDestroy(workAgent2.getNetwork().netId);
assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
- mProfileDefaultNetworkCallback);
+ profileDefaultNetworkCallback);
// Callbacks will be unregistered by tearDown()
}
/**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active. Make sure they behave as expected whether
+ * there is a general default network or not.
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDown() throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ registerDefaultNetworkCallbacks();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), false,
+ testHandle, mProfileDefaultNetworkCallback, null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active. Make sure they behave as expected whether
+ * there is a general default network or not when configured to not fallback to default network.
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithNoFallback() throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), false,
+ testHandle, mProfileDefaultNetworkCallback, null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active. Make sure they behave as expected whether
+ * there is a general default network or not when configured to not fallback to default network
+ * along with already connected enterprise work agent
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithNoFallbackWithAlreadyConnectedWorkAgent()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true, testHandle,
+ mProfileDefaultNetworkCallback, null);
+ }
+
+ /**
+ * Make sure per-profile networking preference for specific uid of test handle
+ * behaves as expected
+ */
+ @Test
+ public void testPreferenceForDefaultUidOfTestHandle() throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder.setIncludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID)));
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), false, testHandle,
+ mProfileDefaultNetworkCallback, null);
+ }
+
+ /**
+ * Make sure per-profile networking preference for specific uid of test handle
+ * behaves as expected
+ */
+ @Test
+ public void testPreferenceForSpecificUidOfOnlyOneApp() throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder.setIncludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), false,
+ testHandle, mProfileDefaultNetworkCallbackAsAppUid2, null);
+ }
+
+ /**
+ * Make sure per-profile networking preference for specific uid of test handle
+ * behaves as expected
+ */
+ @Test
+ public void testPreferenceForDisallowSpecificUidOfApp() throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder.setExcludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), false,
+ testHandle, mProfileDefaultNetworkCallback,
+ mProfileDefaultNetworkCallbackAsAppUid2);
+ }
+
+ /**
+ * Make sure per-profile networking preference for specific uid of test handle
+ * invalid uid inputs
+ */
+ @Test
+ public void testPreferenceForInvalidUids() throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder.setExcludedUids(
+ List.of(testHandle.getUid(0) - 1));
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ Assert.assertThrows(IllegalArgumentException.class, () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener));
+
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setIncludedUids(
+ List.of(testHandle.getUid(0) - 1));
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener));
+
+
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setIncludedUids(
+ List.of(testHandle.getUid(0) - 1));
+ profileNetworkPreferenceBuilder.setExcludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener));
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder2.setIncludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
+ profileNetworkPreferenceBuilder.setIncludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder.build(),
+ profileNetworkPreferenceBuilder2.build()),
+ r -> r.run(), listener));
+
+ profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder2.setExcludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
+ profileNetworkPreferenceBuilder.setExcludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder.build(),
+ profileNetworkPreferenceBuilder2.build()),
+ r -> r.run(), listener));
+
+ profileNetworkPreferenceBuilder2.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder2.setExcludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
+ profileNetworkPreferenceBuilder.setExcludedUids(
+ List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder.build(),
+ profileNetworkPreferenceBuilder2.build()),
+ r -> r.run(), listener));
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active. Make sure they behave as expected whether
+ * there is a general default network or not when configured to fallback to default network
+ * along with already connected enterprise work agent
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithFallbackWithAlreadyConnectedWorkAgent()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true,
+ testHandle, mProfileDefaultNetworkCallback,
+ null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active for a given enterprise identifier
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithDefaultEnterpriseId()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true,
+ testHandle, mProfileDefaultNetworkCallback,
+ null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active for a given enterprise identifier
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithId2()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(
+ NetworkCapabilities.NET_ENTERPRISE_ID_2);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true,
+ testHandle, mProfileDefaultNetworkCallback, null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active for a given enterprise identifier
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithInvalidId()
+ throws Exception {
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(0);
+ registerDefaultNetworkCallbacks();
+ assertThrows("Should not be able to set invalid enterprise id",
+ IllegalStateException.class, () -> profileNetworkPreferenceBuilder.build());
+ }
+
+ /**
* Test that, in a given networking context, calling setPreferenceForUser to set per-profile
* defaults on then off works as expected.
*/
@@ -14052,10 +14465,16 @@
public void testProfileNetworkPrefWrongPreference() throws Exception {
final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
mServiceContext.setWorkProfile(testHandle, true);
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK + 1);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
assertThrows("Should not be able to set an illegal preference",
IllegalArgumentException.class,
- () -> mCm.setProfileNetworkPreference(testHandle,
- PROFILE_NETWORK_PREFERENCE_ENTERPRISE + 1, null, null));
+ () -> mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ null, null));
}
/**
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
index 5bbbe40..45f3d3c 100644
--- a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -60,6 +60,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.system.Os;
import android.test.mock.MockContext;
import android.util.ArraySet;
@@ -188,9 +189,15 @@
}
}
+ private IpSecService.Dependencies makeDependencies() throws RemoteException {
+ final IpSecService.Dependencies deps = mock(IpSecService.Dependencies.class);
+ when(deps.getNetdInstance(mTestContext)).thenReturn(mMockNetd);
+ return deps;
+ }
+
INetd mMockNetd;
PackageManager mMockPkgMgr;
- IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
+ IpSecService.Dependencies mDeps;
IpSecService mIpSecService;
Network fakeNetwork = new Network(0xAB);
int mUid = Os.getuid();
@@ -219,11 +226,8 @@
public void setUp() throws Exception {
mMockNetd = mock(INetd.class);
mMockPkgMgr = mock(PackageManager.class);
- mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
- mIpSecService = new IpSecService(mTestContext, mMockIpSecSrvConfig);
-
- // Injecting mock netd
- when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
+ mDeps = makeDependencies();
+ mIpSecService = new IpSecService(mTestContext, mDeps);
// PackageManager should always return true (feature flag tests in IpSecServiceTest)
when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
diff --git a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
index 6957d51..5c7ca6f 100644
--- a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -57,14 +57,14 @@
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
public class IpSecServiceRefcountedResourceTest {
Context mMockContext;
- IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
+ IpSecService.Dependencies mMockDeps;
IpSecService mIpSecService;
@Before
public void setUp() throws Exception {
mMockContext = mock(Context.class);
- mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
- mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
+ mMockDeps = mock(IpSecService.Dependencies.class);
+ mIpSecService = new IpSecService(mMockContext, mMockDeps);
}
private void assertResourceState(
diff --git a/tests/unit/java/com/android/server/IpSecServiceTest.java b/tests/unit/java/com/android/server/IpSecServiceTest.java
index fabd6f1..7e6b157 100644
--- a/tests/unit/java/com/android/server/IpSecServiceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceTest.java
@@ -46,6 +46,7 @@
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.RemoteException;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;
@@ -122,24 +123,22 @@
Context mMockContext;
INetd mMockNetd;
- IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
+ IpSecService.Dependencies mDeps;
IpSecService mIpSecService;
@Before
public void setUp() throws Exception {
mMockContext = mock(Context.class);
mMockNetd = mock(INetd.class);
- mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
- mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
-
- // Injecting mock netd
- when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
+ mDeps = makeDependencies();
+ mIpSecService = new IpSecService(mMockContext, mDeps);
+ assertNotNull(mIpSecService);
}
- @Test
- public void testIpSecServiceCreate() throws InterruptedException {
- IpSecService ipSecSrv = IpSecService.create(mMockContext);
- assertNotNull(ipSecSrv);
+ private IpSecService.Dependencies makeDependencies() throws RemoteException {
+ final IpSecService.Dependencies deps = mock(IpSecService.Dependencies.class);
+ when(deps.getNetdInstance(mMockContext)).thenReturn(mMockNetd);
+ return deps;
}
@Test
@@ -611,7 +610,7 @@
public void testOpenUdpEncapSocketTagsSocket() throws Exception {
IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class);
IpSecService testIpSecService = new IpSecService(
- mMockContext, mMockIpSecSrvConfig, mockTagger);
+ mMockContext, mDeps, mockTagger);
IpSecUdpEncapResponse udpEncapResp =
testIpSecService.openUdpEncapsulationSocket(0, new Binder());
diff --git a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
index ea29da0..0c58582 100644
--- a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
+++ b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -32,6 +33,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.INetdUnsolicitedEventListener;
import android.net.LinkAddress;
@@ -71,6 +73,7 @@
public class NetworkManagementServiceTest {
private NetworkManagementService mNMService;
@Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
@Mock private IBatteryStats.Stub mBatteryStatsService;
@Mock private INetd.Stub mNetdService;
@@ -113,6 +116,9 @@
MockitoAnnotations.initMocks(this);
doNothing().when(mNetdService)
.registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ eq(ConnectivityManager.class));
+ doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
// Start the service and wait until it connects to our socket.
mNMService = NetworkManagementService.create(mContext, mDeps);
}
@@ -239,6 +245,7 @@
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
assertTrue("Should be true since mobile data usage is restricted",
mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).updateMeteredNetworkDenyList(TEST_UID, true /* enabled */);
mNMService.setDataSaverModeEnabled(true);
verify(mNetdService).bandwidthEnableDataSaver(true);
@@ -246,13 +253,16 @@
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
assertTrue("Should be true since data saver is on and the uid is not allowlisted",
mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).updateMeteredNetworkDenyList(TEST_UID, true /* false */);
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
assertFalse("Should be false since data saver is on and the uid is allowlisted",
mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).updateMeteredNetworkAllowList(TEST_UID, true /* enabled */);
// remove uid from allowlist and turn datasaver off again
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
+ verify(mCm).updateMeteredNetworkAllowList(TEST_UID, false /* enabled */);
mNMService.setDataSaverModeEnabled(false);
verify(mNetdService).bandwidthEnableDataSaver(false);
assertFalse("Network should not be restricted when data saver is off",
@@ -306,12 +316,14 @@
for (int chain : chains) {
final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
mNMService.setFirewallChainEnabled(chain, true);
+ verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
for (int state : states) {
mNMService.setFirewallUidRule(chain, TEST_UID, state);
assertEquals(errorMsg.apply(chain, state),
expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
}
mNMService.setFirewallChainEnabled(chain, false);
+ verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
}
}
}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 4172553..6d1d765 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -92,7 +92,6 @@
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Mock Context mContext;
@Mock ContentResolver mResolver;
- @Mock NsdService.NsdSettings mSettings;
NativeCallbackReceiver mDaemonCallback;
@Spy DaemonConnection mDaemon = new DaemonConnection(mDaemonCallback);
HandlerThread mThread;
@@ -129,7 +128,6 @@
@Test
@DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
public void testPreSClients() throws Exception {
- when(mSettings.isEnabled()).thenReturn(true);
NsdService service = makeService();
// Pre S client connected, the daemon should be started.
@@ -160,7 +158,6 @@
@Test
@EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
public void testNoDaemonStartedWhenClientsConnect() throws Exception {
- when(mSettings.isEnabled()).thenReturn(true);
final NsdService service = makeService();
// Creating an NsdManager will not cause any cmds executed, which means
@@ -197,7 +194,6 @@
@Test
@EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
public void testClientRequestsAreGCedAtDisconnection() throws Exception {
- when(mSettings.isEnabled()).thenReturn(true);
NsdService service = makeService();
NsdManager client = connectClient(service);
@@ -242,8 +238,6 @@
@Test
@EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
public void testCleanupDelayNoRequestActive() throws Exception {
- when(mSettings.isEnabled()).thenReturn(true);
-
NsdService service = makeService();
NsdManager client = connectClient(service);
@@ -277,8 +271,7 @@
mDaemonCallback = callback;
return mDaemon;
};
- final NsdService service = new NsdService(mContext, mSettings,
- mHandler, supplier, CLEANUP_DELAY_MS) {
+ final NsdService service = new NsdService(mContext, mHandler, supplier, CLEANUP_DELAY_MS) {
@Override
public INsdServiceConnector connect(INsdManagerCallback baseCb) {
// Wrap the callback in a transparent mock, to mock asBinder returning a
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
new file mode 100644
index 0000000..84e02ce
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -0,0 +1,385 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import static android.net.INetd.IF_STATE_UP;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
+import static com.android.server.connectivity.ClatCoordinator.CLAT_MAX_MTU;
+import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_PREFIX_LEN;
+import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_STRING;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+
+import android.annotation.NonNull;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.Objects;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class ClatCoordinatorTest {
+ private static final String BASE_IFACE = "test0";
+ private static final String STACKED_IFACE = "v4-test0";
+ private static final int BASE_IFINDEX = 1000;
+
+ private static final IpPrefix NAT64_IP_PREFIX = new IpPrefix("64:ff9b::/96");
+ private static final String NAT64_PREFIX_STRING = "64:ff9b::";
+ private static final int GOOGLE_DNS_4 = 0x08080808; // 8.8.8.8
+ private static final int NETID = 42;
+
+ // The test fwmark means: PERMISSION_SYSTEM (0x2), protectedFromVpn: true,
+ // explicitlySelected: true, netid: 42. For bit field structure definition, see union Fwmark in
+ // system/netd/include/Fwmark.h
+ private static final int MARK = 0xb002a;
+
+ private static final String XLAT_LOCAL_IPV4ADDR_STRING = "192.0.0.46";
+ private static final String XLAT_LOCAL_IPV6ADDR_STRING = "2001:db8:0:b11::464";
+ private static final int CLATD_PID = 10483;
+
+ private static final int TUN_FD = 534;
+ private static final int RAW_SOCK_FD = 535;
+ private static final int PACKET_SOCK_FD = 536;
+ private static final ParcelFileDescriptor TUN_PFD = new ParcelFileDescriptor(
+ new FileDescriptor());
+ private static final ParcelFileDescriptor RAW_SOCK_PFD = new ParcelFileDescriptor(
+ new FileDescriptor());
+ private static final ParcelFileDescriptor PACKET_SOCK_PFD = new ParcelFileDescriptor(
+ new FileDescriptor());
+
+ @Mock private INetd mNetd;
+ @Spy private TestDependencies mDeps = new TestDependencies();
+
+ /**
+ * The dependency injection class is used to mock the JNI functions and system functions
+ * for clatd coordinator control plane. Note that any testing used JNI functions need to
+ * be overridden to avoid calling native methods.
+ */
+ protected class TestDependencies extends ClatCoordinator.Dependencies {
+ /**
+ * Get netd.
+ */
+ @Override
+ public INetd getNetd() {
+ return mNetd;
+ }
+
+ /**
+ * @see ParcelFileDescriptor#adoptFd(int).
+ */
+ @Override
+ public ParcelFileDescriptor adoptFd(int fd) {
+ switch (fd) {
+ case TUN_FD:
+ return TUN_PFD;
+ case RAW_SOCK_FD:
+ return RAW_SOCK_PFD;
+ case PACKET_SOCK_FD:
+ return PACKET_SOCK_PFD;
+ default:
+ fail("unsupported arg: " + fd);
+ return null;
+ }
+ }
+
+ /**
+ * Get interface index for a given interface.
+ */
+ @Override
+ public int getInterfaceIndex(String ifName) {
+ if (BASE_IFACE.equals(ifName)) {
+ return BASE_IFINDEX;
+ }
+ fail("unsupported arg: " + ifName);
+ return -1;
+ }
+
+ /**
+ * Create tun interface for a given interface name.
+ */
+ @Override
+ public int createTunInterface(@NonNull String tuniface) throws IOException {
+ if (STACKED_IFACE.equals(tuniface)) {
+ return TUN_FD;
+ }
+ fail("unsupported arg: " + tuniface);
+ return -1;
+ }
+
+ /**
+ * Pick an IPv4 address for clat.
+ */
+ @Override
+ public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
+ throws IOException {
+ if (INIT_V4ADDR_STRING.equals(v4addr) && INIT_V4ADDR_PREFIX_LEN == prefixlen) {
+ return XLAT_LOCAL_IPV4ADDR_STRING;
+ }
+ fail("unsupported args: " + v4addr + ", " + prefixlen);
+ return null;
+ }
+
+ /**
+ * Generate a checksum-neutral IID.
+ */
+ @Override
+ public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
+ @NonNull String prefix64) throws IOException {
+ if (BASE_IFACE.equals(iface) && XLAT_LOCAL_IPV4ADDR_STRING.equals(v4)
+ && NAT64_PREFIX_STRING.equals(prefix64)) {
+ return XLAT_LOCAL_IPV6ADDR_STRING;
+ }
+ fail("unsupported args: " + iface + ", " + v4 + ", " + prefix64);
+ return null;
+ }
+
+ /**
+ * Detect MTU.
+ */
+ @Override
+ public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
+ throws IOException {
+ if (NAT64_PREFIX_STRING.equals(platSubnet) && GOOGLE_DNS_4 == platSuffix
+ && MARK == mark) {
+ return ETHER_MTU;
+ }
+ fail("unsupported args: " + platSubnet + ", " + platSuffix + ", " + mark);
+ return -1;
+ }
+
+ /**
+ * Open IPv6 raw socket and set SO_MARK.
+ */
+ @Override
+ public int openRawSocket6(int mark) throws IOException {
+ if (mark == MARK) {
+ return RAW_SOCK_FD;
+ }
+ fail("unsupported arg: " + mark);
+ return -1;
+ }
+
+ /**
+ * Open packet socket.
+ */
+ @Override
+ public int openPacketSocket() throws IOException {
+ // assume that open socket always successfully because there is no argument to check.
+ return PACKET_SOCK_FD;
+ }
+
+ /**
+ * Add anycast setsockopt.
+ */
+ @Override
+ public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex)
+ throws IOException {
+ if (Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), sock)
+ && XLAT_LOCAL_IPV6ADDR_STRING.equals(v6)
+ && BASE_IFINDEX == ifindex) return;
+ fail("unsupported args: " + sock + ", " + v6 + ", " + ifindex);
+ }
+
+ /**
+ * Configure packet socket.
+ */
+ @Override
+ public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex)
+ throws IOException {
+ if (Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), sock)
+ && XLAT_LOCAL_IPV6ADDR_STRING.equals(v6)
+ && BASE_IFINDEX == ifindex) return;
+ fail("unsupported args: " + sock + ", " + v6 + ", " + ifindex);
+ }
+
+ /**
+ * Start clatd.
+ */
+ @Override
+ public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
+ @NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96,
+ @NonNull String v4, @NonNull String v6) throws IOException {
+ if (Objects.equals(TUN_PFD.getFileDescriptor(), tunfd)
+ && Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), readsock6)
+ && Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), writesock6)
+ && BASE_IFACE.equals(iface)
+ && NAT64_PREFIX_STRING.equals(pfx96)
+ && XLAT_LOCAL_IPV4ADDR_STRING.equals(v4)
+ && XLAT_LOCAL_IPV6ADDR_STRING.equals(v6)) {
+ return CLATD_PID;
+ }
+ fail("unsupported args: " + tunfd + ", " + readsock6 + ", " + writesock6 + ", "
+ + ", " + iface + ", " + v4 + ", " + v6);
+ return -1;
+ }
+
+ /**
+ * Stop clatd.
+ */
+ public void stopClatd(@NonNull String iface, @NonNull String pfx96, @NonNull String v4,
+ @NonNull String v6, int pid) throws IOException {
+ if (pid == -1) {
+ fail("unsupported arg: " + pid);
+ }
+ }
+ };
+
+ @NonNull
+ private ClatCoordinator makeClatCoordinator() throws Exception {
+ final ClatCoordinator coordinator = new ClatCoordinator(mDeps);
+ return coordinator;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private boolean assertContainsFlag(String[] flags, String match) {
+ for (String flag : flags) {
+ if (flag.equals(match)) return true;
+ }
+ fail("Missing flag: " + match);
+ return false;
+ }
+
+ @Test
+ public void testStartStopClatd() throws Exception {
+ final ClatCoordinator coordinator = makeClatCoordinator();
+ final InOrder inOrder = inOrder(mNetd, mDeps);
+ clearInvocations(mNetd, mDeps);
+
+ // [1] Start clatd.
+ final String addr6For464xlat = coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
+ assertEquals(XLAT_LOCAL_IPV6ADDR_STRING, addr6For464xlat);
+
+ // Pick an IPv4 address.
+ inOrder.verify(mDeps).selectIpv4Address(eq(INIT_V4ADDR_STRING),
+ eq(INIT_V4ADDR_PREFIX_LEN));
+
+ // Generate a checksum-neutral IID.
+ inOrder.verify(mDeps).generateIpv6Address(eq(BASE_IFACE),
+ eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(NAT64_PREFIX_STRING));
+
+ // Open, configure and bring up the tun interface.
+ inOrder.verify(mDeps).createTunInterface(eq(STACKED_IFACE));
+ inOrder.verify(mDeps).adoptFd(eq(TUN_FD));
+ inOrder.verify(mNetd).interfaceSetEnableIPv6(eq(STACKED_IFACE), eq(false /* enable */));
+ inOrder.verify(mDeps).detectMtu(eq(NAT64_PREFIX_STRING), eq(GOOGLE_DNS_4), eq(MARK));
+ inOrder.verify(mNetd).interfaceSetMtu(eq(STACKED_IFACE),
+ eq(1472 /* ETHER_MTU(1500) - MTU_DELTA(28) */));
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+ STACKED_IFACE.equals(cfg.ifName)
+ && XLAT_LOCAL_IPV4ADDR_STRING.equals(cfg.ipv4Addr)
+ && (32 == cfg.prefixLength)
+ && "".equals(cfg.hwAddr)
+ && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+
+ // Open and configure 464xlat read/write sockets.
+ inOrder.verify(mDeps).openPacketSocket();
+ inOrder.verify(mDeps).adoptFd(eq(PACKET_SOCK_FD));
+ inOrder.verify(mDeps).openRawSocket6(eq(MARK));
+ inOrder.verify(mDeps).adoptFd(eq(RAW_SOCK_FD));
+ inOrder.verify(mDeps).getInterfaceIndex(eq(BASE_IFACE));
+ inOrder.verify(mDeps).addAnycastSetsockopt(
+ argThat(fd -> Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), fd)),
+ eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(BASE_IFINDEX));
+ inOrder.verify(mDeps).configurePacketSocket(
+ argThat(fd -> Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), fd)),
+ eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(BASE_IFINDEX));
+
+ // Start clatd.
+ inOrder.verify(mDeps).startClatd(
+ argThat(fd -> Objects.equals(TUN_PFD.getFileDescriptor(), fd)),
+ argThat(fd -> Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), fd)),
+ argThat(fd -> Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), fd)),
+ eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
+ eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING));
+ inOrder.verifyNoMoreInteractions();
+
+ // [2] Start clatd again failed.
+ assertThrows("java.io.IOException: Clatd is already running on test0 (pid 10483)",
+ IOException.class,
+ () -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
+
+ // [3] Expect clatd to stop successfully.
+ coordinator.clatStop();
+ inOrder.verify(mDeps).stopClatd(eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
+ eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(CLATD_PID));
+ inOrder.verifyNoMoreInteractions();
+
+ // [4] Expect an IO exception while stopping a clatd that doesn't exist.
+ assertThrows("java.io.IOException: Clatd has not started", IOException.class,
+ () -> coordinator.clatStop());
+ inOrder.verify(mDeps, never()).stopClatd(anyString(), anyString(), anyString(),
+ anyString(), anyInt());
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testGetFwmark() throws Exception {
+ assertEquals(0xb0064, ClatCoordinator.getFwmark(100));
+ assertEquals(0xb03e8, ClatCoordinator.getFwmark(1000));
+ assertEquals(0xb2710, ClatCoordinator.getFwmark(10000));
+ assertEquals(0xbffff, ClatCoordinator.getFwmark(65535));
+ }
+
+ @Test
+ public void testAdjustMtu() throws Exception {
+ // Expected mtu is that IPV6_MIN_MTU(1280) minus MTU_DELTA(28).
+ assertEquals(1252, ClatCoordinator.adjustMtu(-1 /* detect mtu failed */));
+ assertEquals(1252, ClatCoordinator.adjustMtu(500));
+ assertEquals(1252, ClatCoordinator.adjustMtu(1000));
+ assertEquals(1252, ClatCoordinator.adjustMtu(1280));
+
+ // Expected mtu is that the detected mtu minus MTU_DELTA(28).
+ assertEquals(1372, ClatCoordinator.adjustMtu(1400));
+ assertEquals(1472, ClatCoordinator.adjustMtu(ETHER_MTU));
+ assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU));
+
+ // Expected mtu is that CLAT_MAX_MTU(65536) minus MTU_DELTA(28).
+ assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU + 1 /* over maximum mtu */));
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index e2ad00d..ec51537 100644
--- a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -35,10 +35,12 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -60,6 +62,7 @@
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
import android.util.DataUnit;
+import android.util.Range;
import android.util.RecurrenceRule;
import androidx.test.filters.SmallTest;
@@ -68,7 +71,6 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.net.NetworkPolicyManagerInternal;
-import com.android.server.net.NetworkStatsManagerInternal;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -87,6 +89,7 @@
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
+import java.util.Set;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@@ -94,6 +97,7 @@
public class MultipathPolicyTrackerTest {
private static final Network TEST_NETWORK = new Network(123);
private static final int POLICY_SNOOZED = -100;
+ private static final String TEST_IMSI1 = "TEST_IMSI1";
@Mock private Context mContext;
@Mock private Context mUserAllContext;
@@ -105,7 +109,6 @@
@Mock private NetworkPolicyManager mNPM;
@Mock private NetworkStatsManager mStatsManager;
@Mock private NetworkPolicyManagerInternal mNPMI;
- @Mock private NetworkStatsManagerInternal mNetworkStatsManagerInternal;
@Mock private TelephonyManager mTelephonyManager;
private MockContentResolver mContentResolver;
@@ -148,6 +151,7 @@
when(mDeps.getClock()).thenReturn(mClock);
when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
+ when(mTelephonyManager.getSubscriberId()).thenReturn(TEST_IMSI1);
mContentResolver = Mockito.spy(new MockContentResolver(mContext));
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
@@ -162,9 +166,6 @@
LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
LocalServices.addService(NetworkPolicyManagerInternal.class, mNPMI);
- LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
- LocalServices.addService(NetworkStatsManagerInternal.class, mNetworkStatsManagerInternal);
-
mTracker = new MultipathPolicyTracker(mContext, mHandler, mDeps);
}
@@ -199,6 +200,11 @@
when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH))
.thenReturn(subscriptionQuota);
+ // Prepare stats to be mocked.
+ final NetworkStats.Bucket mockedStatsBucket = mock(NetworkStats.Bucket.class);
+ when(mockedStatsBucket.getTxBytes()).thenReturn(usedBytesToday / 3);
+ when(mockedStatsBucket.getRxBytes()).thenReturn(usedBytesToday - usedBytesToday / 3);
+
// Setup user policy warning / limit
if (policyWarning != WARNING_DISABLED || policyLimit != LIMIT_DISABLED) {
final Instant recurrenceStart = Instant.parse("2017-04-01T00:00:00Z");
@@ -212,7 +218,9 @@
final boolean snoozeLimit = policyLimit == POLICY_SNOOZED;
when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[] {
new NetworkPolicy(
- NetworkTemplate.buildTemplateMobileWildcard(),
+ new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
+ .setSubscriberIds(Set.of(TEST_IMSI1))
+ .setMeteredness(android.net.NetworkStats.METERED_YES).build(),
recurrenceRule,
snoozeWarning ? 0 : policyWarning,
snoozeLimit ? 0 : policyLimit,
@@ -222,6 +230,13 @@
true /* metered */,
false /* inferred */)
});
+
+ // Mock stats for this month.
+ final Range<ZonedDateTime> cycleOfTheMonth = recurrenceRule.cycleIterator().next();
+ when(mStatsManager.querySummaryForDevice(any(),
+ eq(cycleOfTheMonth.getLower().toInstant().toEpochMilli()),
+ eq(cycleOfTheMonth.getUpper().toInstant().toEpochMilli())))
+ .thenReturn(mockedStatsBucket);
} else {
when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]);
}
@@ -233,10 +248,10 @@
when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
.thenReturn((int) defaultResSetting);
- when(mNetworkStatsManagerInternal.getNetworkTotalBytes(
- any(),
+ // Mock stats for today.
+ when(mStatsManager.querySummaryForDevice(any(),
eq(startOfDay.toInstant().toEpochMilli()),
- eq(now.toInstant().toEpochMilli()))).thenReturn(usedBytesToday);
+ eq(now.toInstant().toEpochMilli()))).thenReturn(mockedStatsBucket);
ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
@@ -281,7 +296,7 @@
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
}
@Test
@@ -289,8 +304,10 @@
testGetMultipathPreference(
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
- // 29 days from Apr. 2nd to May 1st
- DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyWarning */,
+ // Remaining days are 29 days from Apr. 2nd to May 1st.
+ // Set limit so that 15MB * remaining days will be 5% of the remaining limit,
+ // so it will be 15 * 29 / 0.05 + used bytes.
+ DataUnit.MEGABYTES.toBytes(15 * 29 * 20 + 7) /* policyWarning */,
LIMIT_DISABLED,
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
2_500_000 /* defaultResSetting */,
@@ -298,7 +315,7 @@
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
}
@Test
@@ -306,16 +323,18 @@
testGetMultipathPreference(
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
- // 29 days from Apr. 2nd to May 1st
POLICY_SNOOZED /* policyWarning */,
- DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyLimit */,
+ // Remaining days are 29 days from Apr. 2nd to May 1st.
+ // Set limit so that 15MB * remaining days will be 5% of the remaining limit,
+ // so it will be 15 * 29 / 0.05 + used bytes.
+ DataUnit.MEGABYTES.toBytes(15 * 29 * 20 + 7) /* policyLimit */,
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
2_500_000 /* defaultResSetting */,
false /* roaming */);
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
}
@Test
@@ -332,7 +351,7 @@
// Default global setting should be used: 12 - 7 = 5
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
}
@Test
@@ -347,7 +366,7 @@
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
// Update setting
setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14));
@@ -357,7 +376,7 @@
// Callback must have been re-registered with new setting
verify(mStatsManager, times(1)).unregisterUsageCallback(any());
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
}
@Test
@@ -372,7 +391,7 @@
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
.thenReturn((int) DataUnit.MEGABYTES.toBytes(16));
@@ -383,6 +402,6 @@
// Uses the new setting (16 - 2 = 14MB)
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java b/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java
new file mode 100644
index 0000000..b8c2673
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java
@@ -0,0 +1,354 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.UidRange;
+import android.os.Build;
+import android.util.ArraySet;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for UidRangeUtils.
+ *
+ * Build, install and run with:
+ * runtest frameworks-net -c com.android.server.connectivity.UidRangeUtilsTest
+ */
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class UidRangeUtilsTest {
+ private static void assertInSameRange(@NonNull final String msg,
+ @Nullable final UidRange r1,
+ @Nullable final Set<UidRange> s2) {
+ assertTrue(msg + " : " + s2 + " unexpectedly is not in range of " + r1,
+ UidRangeUtils.isRangeSetInUidRange(r1, s2));
+ }
+
+ private static void assertNotInSameRange(@NonNull final String msg,
+ @Nullable final UidRange r1, @Nullable final Set<UidRange> s2) {
+ assertFalse(msg + " : " + s2 + " unexpectedly is in range of " + r1,
+ UidRangeUtils.isRangeSetInUidRange(r1, s2));
+ }
+
+ @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testRangeSetInUidRange() {
+ final UidRange uids1 = new UidRange(1, 100);
+ final UidRange uids2 = new UidRange(3, 300);
+ final UidRange uids3 = new UidRange(1, 1000);
+ final UidRange uids4 = new UidRange(1, 100);
+ final UidRange uids5 = new UidRange(2, 20);
+ final UidRange uids6 = new UidRange(3, 30);
+
+ assertThrows(NullPointerException.class,
+ () -> UidRangeUtils.isRangeSetInUidRange(null, null));
+ assertThrows(NullPointerException.class,
+ () -> UidRangeUtils.isRangeSetInUidRange(uids1, null));
+
+ final ArraySet<UidRange> set1 = new ArraySet<>();
+ final ArraySet<UidRange> set2 = new ArraySet<>();
+
+ assertThrows(NullPointerException.class,
+ () -> UidRangeUtils.isRangeSetInUidRange(null, set1));
+ assertInSameRange("uids1 <=> empty", uids1, set2);
+
+ set2.add(uids1);
+ assertInSameRange("uids1 <=> uids1", uids1, set2);
+
+ set2.clear();
+ set2.add(uids2);
+ assertNotInSameRange("uids1 <=> uids2", uids1, set2);
+ set2.clear();
+ set2.add(uids3);
+ assertNotInSameRange("uids1 <=> uids3", uids1, set2);
+ set2.clear();
+ set2.add(uids4);
+ assertInSameRange("uids1 <=> uids4", uids1, set2);
+
+ set2.clear();
+ set2.add(uids5);
+ set2.add(uids6);
+ assertInSameRange("uids1 <=> uids5, 6", uids1, set2);
+
+ set2.clear();
+ set2.add(uids2);
+ set2.add(uids6);
+ assertNotInSameRange("uids1 <=> uids2, 6", uids1, set2);
+ }
+
+ @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testRemoveRangeSetFromUidRange() {
+ final UidRange uids1 = new UidRange(1, 100);
+ final UidRange uids2 = new UidRange(3, 300);
+ final UidRange uids3 = new UidRange(1, 1000);
+ final UidRange uids4 = new UidRange(1, 100);
+ final UidRange uids5 = new UidRange(2, 20);
+ final UidRange uids6 = new UidRange(3, 30);
+ final UidRange uids7 = new UidRange(30, 39);
+
+ final UidRange uids8 = new UidRange(1, 1);
+ final UidRange uids9 = new UidRange(21, 100);
+ final UidRange uids10 = new UidRange(1, 2);
+ final UidRange uids11 = new UidRange(31, 100);
+
+ final UidRange uids12 = new UidRange(1, 1);
+ final UidRange uids13 = new UidRange(21, 29);
+ final UidRange uids14 = new UidRange(40, 100);
+
+ final UidRange uids15 = new UidRange(3, 30);
+ final UidRange uids16 = new UidRange(31, 39);
+
+ assertThrows(NullPointerException.class,
+ () -> UidRangeUtils.removeRangeSetFromUidRange(null, null));
+ Set<UidRange> expected = new ArraySet<>();
+ expected.add(uids1);
+ assertThrows(NullPointerException.class,
+ () -> UidRangeUtils.removeRangeSetFromUidRange(uids1, null));
+ assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, new ArraySet<>()));
+
+ expected.clear();
+ final ArraySet<UidRange> set2 = new ArraySet<>();
+ set2.add(uids1);
+ assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2));
+ set2.clear();
+ set2.add(uids4);
+ assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2));
+
+ expected.add(uids10);
+ set2.clear();
+ set2.add(uids2);
+ assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2));
+
+ expected.clear();
+ set2.clear();
+ set2.add(uids3);
+ assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2));
+
+ set2.clear();
+ set2.add(uids3);
+ set2.add(uids6);
+ assertThrows(IllegalArgumentException.class,
+ () -> UidRangeUtils.removeRangeSetFromUidRange(uids1, set2));
+
+ expected.clear();
+ expected.add(uids8);
+ expected.add(uids9);
+ set2.clear();
+ set2.add(uids5);
+ assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2));
+
+ expected.clear();
+ expected.add(uids10);
+ expected.add(uids11);
+ set2.clear();
+ set2.add(uids6);
+ assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2));
+
+ expected.clear();
+ expected.add(uids12);
+ expected.add(uids13);
+ expected.add(uids14);
+ set2.clear();
+ set2.add(uids5);
+ set2.add(uids7);
+ assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2));
+
+ expected.clear();
+ expected.add(uids10);
+ expected.add(uids14);
+ set2.clear();
+ set2.add(uids15);
+ set2.add(uids16);
+ assertEquals(expected, UidRangeUtils.removeRangeSetFromUidRange(uids1, set2));
+ }
+
+ private static void assertRangeOverlaps(@NonNull final String msg,
+ @Nullable final Set<UidRange> s1,
+ @Nullable final Set<UidRange> s2) {
+ assertTrue(msg + " : " + s2 + " unexpectedly does not overlap with " + s1,
+ UidRangeUtils.doesRangeSetOverlap(s1, s2));
+ }
+
+ private static void assertRangeDoesNotOverlap(@NonNull final String msg,
+ @Nullable final Set<UidRange> s1, @Nullable final Set<UidRange> s2) {
+ assertFalse(msg + " : " + s2 + " unexpectedly ovelaps with " + s1,
+ UidRangeUtils.doesRangeSetOverlap(s1, s2));
+ }
+
+ @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testRangeSetOverlap() {
+ final UidRange uids1 = new UidRange(1, 100);
+ final UidRange uids2 = new UidRange(3, 300);
+ final UidRange uids3 = new UidRange(1, 1000);
+ final UidRange uids4 = new UidRange(1, 100);
+ final UidRange uids5 = new UidRange(2, 20);
+ final UidRange uids6 = new UidRange(3, 30);
+ final UidRange uids7 = new UidRange(0, 0);
+ final UidRange uids8 = new UidRange(1, 500);
+ final UidRange uids9 = new UidRange(101, 200);
+
+ assertThrows(NullPointerException.class,
+ () -> UidRangeUtils.doesRangeSetOverlap(null, null));
+
+ final ArraySet<UidRange> set1 = new ArraySet<>();
+ final ArraySet<UidRange> set2 = new ArraySet<>();
+ assertThrows(NullPointerException.class,
+ () -> UidRangeUtils.doesRangeSetOverlap(set1, null));
+ assertThrows(NullPointerException.class,
+ () -> UidRangeUtils.doesRangeSetOverlap(null, set2));
+ assertRangeDoesNotOverlap("empty <=> null", set1, set2);
+
+ set2.add(uids1);
+ set1.add(uids1);
+ assertRangeOverlaps("uids1 <=> uids1", set1, set2);
+
+ set1.clear();
+ set1.add(uids1);
+ set2.clear();
+ set2.add(uids2);
+ assertRangeOverlaps("uids1 <=> uids2", set1, set2);
+
+ set1.clear();
+ set1.add(uids1);
+ set2.clear();
+ set2.add(uids3);
+ assertRangeOverlaps("uids1 <=> uids3", set1, set2);
+
+ set1.clear();
+ set1.add(uids1);
+ set2.clear();
+ set2.add(uids4);
+ assertRangeOverlaps("uids1 <=> uids4", set1, set2);
+
+ set1.clear();
+ set1.add(uids1);
+ set2.clear();
+ set2.add(uids5);
+ set2.add(uids6);
+ assertRangeOverlaps("uids1 <=> uids5,6", set1, set2);
+
+ set1.clear();
+ set1.add(uids1);
+ set2.clear();
+ set2.add(uids7);
+ assertRangeDoesNotOverlap("uids1 <=> uids7", set1, set2);
+
+ set1.clear();
+ set1.add(uids1);
+ set2.clear();
+ set2.add(uids9);
+ assertRangeDoesNotOverlap("uids1 <=> uids9", set1, set2);
+
+ set1.clear();
+ set1.add(uids1);
+ set2.clear();
+ set2.add(uids8);
+ assertRangeOverlaps("uids1 <=> uids8", set1, set2);
+
+
+ set1.clear();
+ set1.add(uids1);
+ set2.clear();
+ set2.add(uids8);
+ set2.add(uids7);
+ assertRangeOverlaps("uids1 <=> uids7, 8", set1, set2);
+ }
+
+ @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testConvertListToUidRange() {
+ final UidRange uids1 = new UidRange(1, 1);
+ final UidRange uids2 = new UidRange(1, 2);
+ final UidRange uids3 = new UidRange(100, 100);
+ final UidRange uids4 = new UidRange(10, 10);
+
+ final UidRange uids5 = new UidRange(10, 14);
+ final UidRange uids6 = new UidRange(20, 24);
+
+ final Set<UidRange> expected = new ArraySet<>();
+ final List<Integer> input = new ArrayList<Integer>();
+
+ assertThrows(NullPointerException.class, () -> UidRangeUtils.convertListToUidRange(null));
+ assertEquals(expected, UidRangeUtils.convertListToUidRange(input));
+
+ input.add(1);
+ expected.add(uids1);
+ assertEquals(expected, UidRangeUtils.convertListToUidRange(input));
+
+ input.add(2);
+ expected.clear();
+ expected.add(uids2);
+ assertEquals(expected, UidRangeUtils.convertListToUidRange(input));
+
+ input.clear();
+ input.add(1);
+ input.add(100);
+ expected.clear();
+ expected.add(uids1);
+ expected.add(uids3);
+ assertEquals(expected, UidRangeUtils.convertListToUidRange(input));
+
+ input.clear();
+ input.add(100);
+ input.add(1);
+ expected.clear();
+ expected.add(uids1);
+ expected.add(uids3);
+ assertEquals(expected, UidRangeUtils.convertListToUidRange(input));
+
+ input.clear();
+ input.add(100);
+ input.add(1);
+ input.add(2);
+ input.add(1);
+ input.add(10);
+ expected.clear();
+ expected.add(uids2);
+ expected.add(uids4);
+ expected.add(uids3);
+ assertEquals(expected, UidRangeUtils.convertListToUidRange(input));
+
+ input.clear();
+ input.add(10);
+ input.add(11);
+ input.add(12);
+ input.add(13);
+ input.add(14);
+ input.add(20);
+ input.add(21);
+ input.add(22);
+ input.add(23);
+ input.add(24);
+ expected.clear();
+ expected.add(uids5);
+ expected.add(uids6);
+ assertEquals(expected, UidRangeUtils.convertListToUidRange(input));
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index fd9aefa..33c0868 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -17,6 +17,9 @@
package com.android.server.connectivity;
import static android.Manifest.permission.BIND_VPN_SERVICE;
+import static android.Manifest.permission.CONTROL_VPN;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.UserInfo.FLAG_ADMIN;
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
import static android.content.pm.UserInfo.FLAG_PRIMARY;
@@ -26,6 +29,9 @@
import static android.net.INetd.IF_STATE_UP;
import static android.os.UserHandle.PER_USER_RANGE;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -259,6 +265,10 @@
IpSecManager.Status.OK, TEST_TUNNEL_RESOURCE_ID, TEST_IFACE_NAME);
when(mIpSecService.createTunnelInterface(any(), any(), any(), any(), any()))
.thenReturn(tunnelResp);
+ // The unit test should know what kind of permission it needs and set the permission by
+ // itself, so set the default value of Context#checkCallingOrSelfPermission to
+ // PERMISSION_DENIED.
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
}
private <T> void mockService(Class<T> clazz, String name, T service) {
@@ -511,6 +521,7 @@
@Test
public void testLockdownRuleReversibility() throws Exception {
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
final Vpn vpn = createVpn(primaryUser.id);
final UidRangeParcel[] entireUser = {
new UidRangeParcel(PRI_USER_RANGE.getLower(), PRI_USER_RANGE.getUpper())
@@ -538,6 +549,27 @@
}
@Test
+ public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller()
+ throws Exception {
+ assumeTrue(isAtLeastT());
+ final Vpn vpn = createVpnAndSetupUidChecks();
+ assertThrows(SecurityException.class,
+ () -> vpn.prepare("com.not.vpn.owner", null, VpnManager.TYPE_VPN_SERVICE));
+ assertThrows(SecurityException.class,
+ () -> vpn.prepare(null, "com.not.vpn.owner", VpnManager.TYPE_VPN_SERVICE));
+ assertThrows(SecurityException.class,
+ () -> vpn.prepare("com.not.vpn.owner1", "com.not.vpn.owner2",
+ VpnManager.TYPE_VPN_SERVICE));
+ }
+
+ @Test
+ public void testPrepare_bothOldPackageAndNewPackageAreNull() throws Exception {
+ final Vpn vpn = createVpnAndSetupUidChecks();
+ assertTrue(vpn.prepare(null, null, VpnManager.TYPE_VPN_SERVICE));
+
+ }
+
+ @Test
public void testIsAlwaysOnPackageSupported() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
new file mode 100644
index 0000000..987b7b7
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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 com.android.server.net;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.MacAddress;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.Struct.U32;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class BpfInterfaceMapUpdaterTest {
+ private static final int TEST_INDEX = 1;
+ private static final int TEST_INDEX2 = 2;
+ private static final String TEST_INTERFACE_NAME = "test1";
+ private static final String TEST_INTERFACE_NAME2 = "test2";
+
+ private final TestLooper mLooper = new TestLooper();
+ private BaseNetdUnsolicitedEventListener mListener;
+ private BpfInterfaceMapUpdater mUpdater;
+ @Mock private IBpfMap<U32, InterfaceMapValue> mBpfMap;
+ @Mock private INetd mNetd;
+ @Mock private Context mContext;
+
+ private class TestDependencies extends BpfInterfaceMapUpdater.Dependencies {
+ @Override
+ public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
+ return mBpfMap;
+ }
+
+ @Override
+ public InterfaceParams getInterfaceParams(String ifaceName) {
+ if (ifaceName.equals(TEST_INTERFACE_NAME)) {
+ return new InterfaceParams(TEST_INTERFACE_NAME, TEST_INDEX,
+ MacAddress.ALL_ZEROS_ADDRESS);
+ } else if (ifaceName.equals(TEST_INTERFACE_NAME2)) {
+ return new InterfaceParams(TEST_INTERFACE_NAME2, TEST_INDEX2,
+ MacAddress.ALL_ZEROS_ADDRESS);
+ }
+
+ return null;
+ }
+
+ @Override
+ public INetd getINetd(Context ctx) {
+ return mNetd;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {TEST_INTERFACE_NAME});
+ mUpdater = new BpfInterfaceMapUpdater(mContext, new Handler(mLooper.getLooper()),
+ new TestDependencies());
+ }
+
+ private void verifyStartUpdater() throws Exception {
+ mUpdater.start();
+ mLooper.dispatchAll();
+ final ArgumentCaptor<BaseNetdUnsolicitedEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener.class);
+ verify(mNetd).registerUnsolicitedEventListener(listenerCaptor.capture());
+ mListener = listenerCaptor.getValue();
+ verify(mBpfMap).updateEntry(eq(new U32(TEST_INDEX)),
+ eq(new InterfaceMapValue(TEST_INTERFACE_NAME)));
+ }
+
+ @Test
+ public void testUpdateInterfaceMap() throws Exception {
+ verifyStartUpdater();
+
+ mListener.onInterfaceAdded(TEST_INTERFACE_NAME2);
+ mLooper.dispatchAll();
+ verify(mBpfMap).updateEntry(eq(new U32(TEST_INDEX2)),
+ eq(new InterfaceMapValue(TEST_INTERFACE_NAME2)));
+
+ // Check that when onInterfaceRemoved is called, nothing happens.
+ mListener.onInterfaceRemoved(TEST_INTERFACE_NAME);
+ mLooper.dispatchAll();
+ verifyNoMoreInteractions(mBpfMap);
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 8d7aa4e..0e2c293 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -32,8 +32,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import android.content.Context;
import android.content.res.Resources;
+import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.net.TrafficStats;
import android.net.UnderlyingNetworkInfo;
@@ -54,6 +58,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.io.File;
import java.io.FileOutputStream;
@@ -70,16 +76,22 @@
private File mTestProc;
private NetworkStatsFactory mFactory;
+ @Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mTestProc = TestIoUtils.createTemporaryDirectory("proc");
// The libandroid_servers which have the native method is not available to
// applications. So in order to have a test support native library, the native code
// related to networkStatsFactory is compiled to a minimal native library and loaded here.
System.loadLibrary("networkstatsfactorytestjni");
- mFactory = new NetworkStatsFactory(mTestProc, false);
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ eq(ConnectivityManager.class));
+ doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
+ mFactory = new NetworkStatsFactory(mContext, mTestProc, false);
mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]);
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index e35104e..66dcf6d 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -29,23 +29,22 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
-import android.app.usage.NetworkStatsManager;
import android.net.DataUsageRequest;
import android.net.NetworkIdentity;
+import android.net.NetworkIdentitySet;
import android.net.NetworkStats;
+import android.net.NetworkStatsAccess;
import android.net.NetworkTemplate;
-import android.os.Build;
-import android.os.ConditionVariable;
-import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Messenger;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -53,7 +52,6 @@
import androidx.test.filters.SmallTest;
-import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -72,7 +70,7 @@
*/
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public class NetworkStatsObserversTest {
private static final String TEST_IFACE = "test0";
private static final String TEST_IFACE2 = "test1";
@@ -94,21 +92,15 @@
private static final long WAIT_TIMEOUT_MS = 500;
private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
private static final long BASE_BYTES = 7 * MB_IN_BYTES;
- private static final int INVALID_TYPE = -1;
-
- private long mElapsedRealtime;
private HandlerThread mObserverHandlerThread;
- private Handler mObserverNoopHandler;
-
- private LatchedHandler mHandler;
private NetworkStatsObservers mStatsObservers;
- private Messenger mMessenger;
private ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
private ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
- @Mock private IBinder mockBinder;
+ @Mock private IBinder mUsageCallbackBinder;
+ private TestableUsageCallback mUsageCallback;
@Before
public void setUp() throws Exception {
@@ -124,24 +116,29 @@
}
};
- mHandler = new LatchedHandler(Looper.getMainLooper(), new ConditionVariable());
- mMessenger = new Messenger(mHandler);
-
mActiveIfaces = new ArrayMap<>();
mActiveUidIfaces = new ArrayMap<>();
+ mUsageCallback = new TestableUsageCallback(mUsageCallbackBinder);
}
@Test
public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
- long thresholdTooLowBytes = 1L;
- DataUsageRequest inputRequest = new DataUsageRequest(
+ final long thresholdTooLowBytes = 1L;
+ final DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateWifi, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+ final DataUsageRequest requestByApp = mStatsObservers.register(inputRequest, mUsageCallback,
+ UID_RED, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(requestByApp.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, requestByApp.template));
+ assertEquals(THRESHOLD_BYTES, requestByApp.thresholdInBytes);
+
+ // Verify the threshold requested by system uid won't be overridden.
+ final DataUsageRequest requestBySystem = mStatsObservers.register(inputRequest,
+ mUsageCallback, Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(requestBySystem.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, requestBySystem.template));
+ assertEquals(1, requestBySystem.thresholdInBytes);
}
@Test
@@ -150,7 +147,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
@@ -162,13 +159,13 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
- DataUsageRequest request1 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request1 = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request1.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request1.template));
assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
- DataUsageRequest request2 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request2 = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request2.requestId > request1.requestId);
assertTrue(Objects.equals(sTemplateWifi, request2.template));
@@ -188,17 +185,19 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
- Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ Mockito.verify(mUsageCallbackBinder).linkToDeath(any(IBinder.DeathRecipient.class),
+ anyInt());
mStatsObservers.unregister(request, Process.SYSTEM_UID);
waitForObserverToIdle();
- Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ Mockito.verify(mUsageCallbackBinder).unlinkToDeath(any(IBinder.DeathRecipient.class),
+ anyInt());
}
@Test
@@ -206,17 +205,18 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_RED, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
- Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ Mockito.verify(mUsageCallbackBinder)
+ .linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
mStatsObservers.unregister(request, UID_BLUE);
waitForObserverToIdle();
- Mockito.verifyZeroInteractions(mockBinder);
+ Mockito.verifyZeroInteractions(mUsageCallbackBinder);
}
private NetworkIdentitySet makeTestIdentSet() {
@@ -233,7 +233,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -257,7 +257,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -287,7 +287,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -310,7 +310,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -318,7 +318,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_RED, NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -343,7 +343,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -351,7 +351,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -383,7 +383,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_BLUE, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -408,7 +408,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -416,7 +416,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_RED, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -445,6 +445,5 @@
private void waitForObserverToIdle() {
HandlerUtils.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
- HandlerUtils.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
}
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 4948e66..45f033c 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -41,7 +41,6 @@
import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.SET_FOREGROUND;
-import static android.net.NetworkStats.STATS_PER_UID;
import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
@@ -50,7 +49,6 @@
import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
import static android.net.NetworkTemplate.OEM_MANAGED_NO;
import static android.net.NetworkTemplate.OEM_MANAGED_YES;
-import static android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateMobileWithRatType;
import static android.net.NetworkTemplate.buildTemplateWifi;
@@ -63,30 +61,32 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.AlarmManager;
-import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.net.DataUsageRequest;
-import android.net.INetworkManagementEventObserver;
+import android.net.INetd;
import android.net.INetworkStatsSession;
import android.net.LinkProperties;
import android.net.Network;
@@ -96,17 +96,15 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
+import android.net.TetherStatsParcel;
+import android.net.TetheringManager;
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.net.wifi.WifiInfo;
import android.os.Build;
-import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
import android.os.PowerManager;
import android.os.SimpleClock;
import android.provider.Settings;
@@ -116,8 +114,12 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.LocationPermissionChecker;
+import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.U8;
+import com.android.server.net.NetworkStatsService.AlertObserver;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
import com.android.testutils.DevSdkIgnoreRule;
@@ -158,9 +160,9 @@
private static final String IMSI_1 = "310004";
private static final String IMSI_2 = "310260";
- private static final String TEST_SSID = "AndroidAP";
+ private static final String TEST_WIFI_NETWORK_KEY = "WifiNetworkKey";
- private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID);
+ private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_WIFI_NETWORK_KEY);
private static NetworkTemplate sTemplateCarrierWifi1 =
buildTemplateWifi(NetworkTemplate.WIFI_NETWORKID_ALL, IMSI_1);
private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
@@ -181,20 +183,28 @@
private File mStatsDir;
private MockContext mServiceContext;
private @Mock TelephonyManager mTelephonyManager;
- private @Mock INetworkManagementService mNetManager;
+ private static @Mock WifiInfo sWifiInfo;
+ private @Mock INetd mNetd;
+ private @Mock TetheringManager mTetheringManager;
private @Mock NetworkStatsFactory mStatsFactory;
private @Mock NetworkStatsSettings mSettings;
- private @Mock IBinder mBinder;
+ private @Mock IBinder mUsageCallbackBinder;
+ private TestableUsageCallback mUsageCallback;
private @Mock AlarmManager mAlarmManager;
@Mock
private NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor;
+ private @Mock BpfInterfaceMapUpdater mBpfInterfaceMapUpdater;
private HandlerThread mHandlerThread;
+ @Mock
+ private LocationPermissionChecker mLocationPermissionChecker;
+ private @Mock IBpfMap<U32, U8> mUidCounterSetMap;
private NetworkStatsService mService;
private INetworkStatsSession mSession;
- private INetworkManagementEventObserver mNetworkObserver;
+ private AlertObserver mAlertObserver;
private ContentObserver mContentObserver;
private Handler mHandler;
+ private TetheringManager.TetheringEventCallback mTetheringEventCallback;
private class MockContext extends BroadcastInterceptingContext {
private final Context mBaseContext;
@@ -207,6 +217,7 @@
@Override
public Object getSystemService(String name) {
if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
+ if (Context.TETHERING_SERVICE.equals(name)) return mTetheringManager;
return mBaseContext.getSystemService(name);
}
@@ -237,11 +248,28 @@
return currentTimeMillis();
}
};
+
+ @NonNull
+ private static TetherStatsParcel buildTetherStatsParcel(String iface, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, int ifIndex) {
+ TetherStatsParcel parcel = new TetherStatsParcel();
+ parcel.iface = iface;
+ parcel.rxBytes = rxBytes;
+ parcel.rxPackets = rxPackets;
+ parcel.txBytes = txBytes;
+ parcel.txPackets = txPackets;
+ parcel.ifIndex = ifIndex;
+ return parcel;
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
final Context context = InstrumentationRegistry.getContext();
mServiceContext = new MockContext(context);
+ when(mLocationPermissionChecker.checkCallersLocationPermission(
+ any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
+ when(sWifiInfo.getNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
mStatsDir = TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
@@ -251,7 +279,7 @@
mHandlerThread = new HandlerThread("HandlerThread");
final NetworkStatsService.Dependencies deps = makeDependencies();
- mService = new NetworkStatsService(mServiceContext, mNetManager, mAlarmManager, wakeLock,
+ mService = new NetworkStatsService(mServiceContext, mNetd, mAlarmManager, wakeLock,
mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), mStatsDir,
getBaseDir(mStatsDir), deps);
@@ -276,11 +304,20 @@
mSession = mService.openSession();
assertNotNull("openSession() failed", mSession);
- // catch INetworkManagementEventObserver during systemReady()
- ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
- ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
- verify(mNetManager).registerObserver(networkObserver.capture());
- mNetworkObserver = networkObserver.getValue();
+ // Catch AlertObserver during systemReady().
+ final ArgumentCaptor<AlertObserver> alertObserver =
+ ArgumentCaptor.forClass(AlertObserver.class);
+ verify(mNetd).registerUnsolicitedEventListener(alertObserver.capture());
+ mAlertObserver = alertObserver.getValue();
+
+ // Catch TetheringEventCallback during systemReady().
+ ArgumentCaptor<TetheringManager.TetheringEventCallback> tetheringEventCbCaptor =
+ ArgumentCaptor.forClass(TetheringManager.TetheringEventCallback.class);
+ verify(mTetheringManager).registerTetheringEventCallback(
+ any(), tetheringEventCbCaptor.capture());
+ mTetheringEventCallback = tetheringEventCbCaptor.getValue();
+
+ mUsageCallback = new TestableUsageCallback(mUsageCallbackBinder);
}
@NonNull
@@ -293,7 +330,7 @@
@Override
public NetworkStatsSubscriptionsMonitor makeSubscriptionsMonitor(
- @NonNull Context context, @NonNull Looper looper, @NonNull Executor executor,
+ @NonNull Context context, @NonNull Executor executor,
@NonNull NetworkStatsService service) {
return mNetworkStatsSubscriptionsMonitor;
@@ -306,6 +343,21 @@
return mContentObserver = super.makeContentObserver(handler, settings, monitor);
}
+ @Override
+ public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
+ return mLocationPermissionChecker;
+ }
+
+ @Override
+ public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
+ @NonNull Context ctx, @NonNull Handler handler) {
+ return mBpfInterfaceMapUpdater;
+ }
+
+ @Override
+ public IBpfMap<U32, U8> getUidCounterSetMap() {
+ return mUidCounterSetMap;
+ }
};
}
@@ -314,7 +366,7 @@
mServiceContext = null;
mStatsDir = null;
- mNetManager = null;
+ mNetd = null;
mSettings = null;
mSession.close();
@@ -358,7 +410,7 @@
// verify service recorded history
assertNetworkTotal(sTemplateCarrierWifi1, 1024L, 1L, 2048L, 2L, 0);
- // verify service recorded history for wifi with SSID filter
+ // verify service recorded history for wifi with WiFi Network Key filter
assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
@@ -368,7 +420,7 @@
// verify service recorded history
assertNetworkTotal(sTemplateCarrierWifi1, 4096L, 4L, 8192L, 8L, 0);
- // verify service recorded history for wifi with SSID filter
+ // verify service recorded history for wifi with WiFi Network Key filter
assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
}
@@ -431,8 +483,11 @@
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
mService.setUidForeground(UID_RED, false);
+ verify(mUidCounterSetMap, never()).deleteEntry(any());
mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
mService.setUidForeground(UID_RED, true);
+ verify(mUidCounterSetMap).updateEntry(
+ eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
forcePollAndWaitForIdle();
@@ -774,28 +829,31 @@
@Test
public void testMobileStatsOemManaged() throws Exception {
final NetworkTemplate templateOemPaid = new NetworkTemplate(MATCH_MOBILE_WILDCARD,
- /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null,
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_PAID,
- SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ /*subscriberId=*/null, /*matchSubscriberIds=*/null,
+ /*matchWifiNetworkKeys=*/new String[0], METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_PAID, SUBSCRIBER_ID_MATCH_RULE_EXACT);
final NetworkTemplate templateOemPrivate = new NetworkTemplate(MATCH_MOBILE_WILDCARD,
- /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null,
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_PRIVATE,
- SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ /*subscriberId=*/null, /*matchSubscriberIds=*/null,
+ /*matchWifiNetworkKeys=*/new String[0], METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_PRIVATE, SUBSCRIBER_ID_MATCH_RULE_EXACT);
final NetworkTemplate templateOemAll = new NetworkTemplate(MATCH_MOBILE_WILDCARD,
- /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null,
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_PAID | OEM_PRIVATE, SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ /*subscriberId=*/null, /*matchSubscriberIds=*/null,
+ /*matchWifiNetworkKeys=*/new String[0], METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_PAID | OEM_PRIVATE,
+ SUBSCRIBER_ID_MATCH_RULE_EXACT);
final NetworkTemplate templateOemYes = new NetworkTemplate(MATCH_MOBILE_WILDCARD,
- /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null,
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_YES,
+ /*subscriberId=*/null, /*matchSubscriberIds=*/null,
+ /*matchWifiNetworkKeys=*/new String[0], METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_YES,
SUBSCRIBER_ID_MATCH_RULE_EXACT);
final NetworkTemplate templateOemNone = new NetworkTemplate(MATCH_MOBILE_WILDCARD,
- /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null,
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_NO,
+ /*subscriberId=*/null, /*matchSubscriberIds=*/null,
+ /*matchWifiNetworkKeys=*/new String[0], METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_NO,
SUBSCRIBER_ID_MATCH_RULE_EXACT);
// OEM_PAID network comes online.
@@ -954,7 +1012,7 @@
}
@Test
- public void testDetailedUidStats() throws Exception {
+ public void testUidStatsForTransport() throws Exception {
// pretend that network comes online
expectDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
@@ -980,7 +1038,7 @@
.insertEntry(entry3));
mService.incrementOperationCount(UID_RED, 0xF00D, 1);
- NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL);
+ NetworkStats stats = mService.getUidStatsForTransport(NetworkCapabilities.TRANSPORT_WIFI);
assertEquals(3, stats.size());
entry1.operations = 1;
@@ -991,68 +1049,6 @@
}
@Test
- public void testDetailedUidStats_Filtered() throws Exception {
- // pretend that network comes online
- expectDefaultSettings();
-
- final String stackedIface = "stacked-test0";
- final LinkProperties stackedProp = new LinkProperties();
- stackedProp.setInterfaceName(stackedIface);
- final NetworkStateSnapshot wifiState = buildWifiState();
- wifiState.getLinkProperties().addStackedLink(stackedProp);
- NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {wifiState};
-
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
-
- mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
- new UnderlyingNetworkInfo[0]);
-
- NetworkStats.Entry uidStats = new NetworkStats.Entry(
- TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
- // Stacked on matching interface
- NetworkStats.Entry tetheredStats1 = new NetworkStats.Entry(
- stackedIface, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
- // Different interface
- NetworkStats.Entry tetheredStats2 = new NetworkStats.Entry(
- "otherif", UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
-
- final String[] ifaceFilter = new String[] { TEST_IFACE };
- final String[] augmentedIfaceFilter = new String[] { stackedIface, TEST_IFACE };
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- when(mStatsFactory.augmentWithStackedInterfaces(eq(ifaceFilter)))
- .thenReturn(augmentedIfaceFilter);
- when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL)))
- .thenReturn(new NetworkStats(getElapsedRealtime(), 1)
- .insertEntry(uidStats));
- when(mNetManager.getNetworkStatsTethering(STATS_PER_UID))
- .thenReturn(new NetworkStats(getElapsedRealtime(), 2)
- .insertEntry(tetheredStats1)
- .insertEntry(tetheredStats2));
-
- NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
-
- // mStatsFactory#readNetworkStatsDetail() has the following invocations:
- // 1) NetworkStatsService#systemReady from #setUp.
- // 2) mService#notifyNetworkStatus in the test above.
- //
- // Additionally, we should have one call from the above call to mService#getDetailedUidStats
- // with the augmented ifaceFilter.
- verify(mStatsFactory, times(2)).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
- verify(mStatsFactory, times(1)).readNetworkStatsDetail(
- eq(UID_ALL),
- eq(augmentedIfaceFilter),
- eq(TAG_ALL));
- assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE));
- assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface));
- assertEquals(2, stats.size());
- assertEquals(uidStats, stats.getValues(0, null));
- assertEquals(tetheredStats1, stats.getValues(1, null));
- }
-
- @Test
public void testForegroundBackground() throws Exception {
// pretend that network comes online
expectDefaultSettings();
@@ -1088,6 +1084,8 @@
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
mService.setUidForeground(UID_RED, true);
+ verify(mUidCounterSetMap).updateEntry(
+ eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
forcePollAndWaitForIdle();
@@ -1232,12 +1230,11 @@
final NetworkStats localUidStats = new NetworkStats(now, 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
// Software per-uid tethering traffic.
- final NetworkStats tetherSwUidStats = new NetworkStats(now, 1)
- .insertEntry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1408L, 10L, 256L, 1L,
- 0L);
+ final TetherStatsParcel[] tetherStatsParcels =
+ {buildTetherStatsParcel(TEST_IFACE, 1408L, 10L, 256L, 1L, 0)};
expectNetworkStatsSummary(swIfaceStats);
- expectNetworkStatsUidDetail(localUidStats, tetherSwUidStats);
+ expectNetworkStatsUidDetail(localUidStats, tetherStatsParcels);
forcePollAndWaitForIdle();
// verify service recorded history
@@ -1264,20 +1261,14 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdInBytes);
- // Create a messenger that waits for callback activity
- ConditionVariable cv = new ConditionVariable(false);
- LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv);
- Messenger messenger = new Messenger(latchedHandler);
-
// Force poll
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
// Register and verify request and that binder was called
- DataUsageRequest request =
- mService.registerUsageCallback(mServiceContext.getOpPackageName(), inputRequest,
- messenger, mBinder);
+ DataUsageRequest request = mService.registerUsageCallback(
+ mServiceContext.getOpPackageName(), inputRequest, mUsageCallback);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
@@ -1286,7 +1277,7 @@
HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
// Make sure that the caller binder gets connected
- verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ verify(mUsageCallbackBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
// modify some number on wifi, and trigger poll event
// not enough traffic to call data usage callback
@@ -1301,7 +1292,7 @@
assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
// make sure callback has not being called
- assertEquals(INVALID_TYPE, latchedHandler.lastMessageType);
+ mUsageCallback.assertNoCallback();
// and bump forward again, with counters going higher. this is
// important, since it will trigger the data usage callback
@@ -1316,23 +1307,21 @@
assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0);
- // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
- assertTrue(cv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.lastMessageType);
- cv.close();
+ // Wait for the caller to invoke expectOnThresholdReached.
+ mUsageCallback.expectOnThresholdReached(request);
// Allow binder to disconnect
- when(mBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt())).thenReturn(true);
+ when(mUsageCallbackBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt()))
+ .thenReturn(true);
// Unregister request
mService.unregisterUsageRequest(request);
- // Wait for the caller to ack receipt of CALLBACK_RELEASED
- assertTrue(cv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.lastMessageType);
+ // Wait for the caller to invoke expectOnCallbackReleased.
+ mUsageCallback.expectOnCallbackReleased(request);
// Make sure that the caller binder gets disconnected
- verify(mBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ verify(mUsageCallbackBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
}
@Test
@@ -1643,6 +1632,43 @@
DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 2);
}
+ @Test
+ public void testTetheringEventCallback_onUpstreamChanged() throws Exception {
+ // Register custom provider and retrieve callback.
+ final TestableNetworkStatsProviderBinder provider =
+ new TestableNetworkStatsProviderBinder();
+ final INetworkStatsProviderCallback cb =
+ mService.registerNetworkStatsProvider("TEST-TETHERING-OFFLOAD", provider);
+ assertNotNull(cb);
+ provider.assertNoCallback();
+
+ // Post upstream changed event, verify the service will pull for stats.
+ mTetheringEventCallback.onUpstreamChanged(WIFI_NETWORK);
+ provider.expectOnRequestStatsUpdate(0 /* unused */);
+ }
+
+ /**
+ * Verify the service will throw exceptions if the template is location sensitive but
+ * the permission is not granted.
+ */
+ @Test
+ public void testEnforceTemplateLocationPermission() throws Exception {
+ when(mLocationPermissionChecker.checkCallersLocationPermission(
+ any(), any(), anyInt(), anyBoolean(), any())).thenReturn(false);
+ initWifiStats(buildWifiState(true, TEST_IFACE, IMSI_1));
+ assertThrows(SecurityException.class, () ->
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0));
+ // Templates w/o wifi network keys can query stats as usual.
+ assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
+ assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
+
+ when(mLocationPermissionChecker.checkCallersLocationPermission(
+ any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
+ assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
+ }
+
private static File getBaseDir(File statsDir) {
File baseDir = new File(statsDir, "netstats");
baseDir.mkdirs();
@@ -1658,7 +1684,8 @@
private void assertNetworkTotal(NetworkTemplate template, long start, long end, long rxBytes,
long rxPackets, long txBytes, long txPackets, int operations) throws Exception {
// verify history API
- final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELD_ALL);
+ final NetworkStatsHistory history =
+ mSession.getHistoryIntervalForNetwork(template, FIELD_ALL, start, end);
assertValues(history, start, end, rxBytes, rxPackets, txBytes, txPackets, operations);
// verify summary API
@@ -1716,16 +1743,17 @@
}
private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
- expectNetworkStatsUidDetail(detail, new NetworkStats(0L, 0));
+ final TetherStatsParcel[] tetherStatsParcels = {};
+ expectNetworkStatsUidDetail(detail, tetherStatsParcels);
}
- private void expectNetworkStatsUidDetail(NetworkStats detail, NetworkStats tetherStats)
- throws Exception {
+ private void expectNetworkStatsUidDetail(NetworkStats detail,
+ TetherStatsParcel[] tetherStatsParcels) throws Exception {
when(mStatsFactory.readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL))
.thenReturn(detail);
// also include tethering details, since they are folded into UID
- when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)).thenReturn(tetherStats);
+ when(mNetd.tetherGetStats()).thenReturn(tetherStatsParcels);
}
private void expectDefaultSettings() throws Exception {
@@ -1787,7 +1815,7 @@
capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !isMetered);
capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true);
capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
- capabilities.setSSID(TEST_SSID);
+ capabilities.setTransportInfo(sWifiInfo);
return new NetworkStateSnapshot(WIFI_NETWORK, capabilities, prop, subscriberId, TYPE_WIFI);
}
@@ -1869,21 +1897,4 @@
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
}
-
- static class LatchedHandler extends Handler {
- private final ConditionVariable mCv;
- int lastMessageType = INVALID_TYPE;
-
- LatchedHandler(Looper looper, ConditionVariable cv) {
- super(looper);
- mCv = cv;
- }
-
- @Override
- public void handleMessage(Message msg) {
- lastMessageType = msg.what;
- mCv.open();
- super.handleMessage(msg);
- }
- }
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 2bc385c..0d34609 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -16,6 +16,9 @@
package com.android.server.net;
+import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -32,15 +35,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.usage.NetworkStatsManager;
import android.content.Context;
-import android.net.NetworkTemplate;
import android.os.Build;
-import android.os.test.TestLooper;
-import android.telephony.NetworkRegistrationInfo;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
+import android.os.Looper;
+import android.os.Parcel;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
+import android.util.SparseArray;
import com.android.internal.util.CollectionUtils;
import com.android.server.net.NetworkStatsSubscriptionsMonitor.RatTypeListener;
@@ -71,26 +74,30 @@
@Mock private Context mContext;
@Mock private SubscriptionManager mSubscriptionManager;
@Mock private TelephonyManager mTelephonyManager;
+ private final SparseArray<TelephonyManager> mTelephonyManagerOfSub = new SparseArray<>();
+ private final SparseArray<RatTypeListener> mRatTypeListenerOfSub = new SparseArray<>();
@Mock private NetworkStatsSubscriptionsMonitor.Delegate mDelegate;
private final List<Integer> mTestSubList = new ArrayList<>();
private final Executor mExecutor = Executors.newSingleThreadExecutor();
private NetworkStatsSubscriptionsMonitor mMonitor;
- private TestLooper mTestLooper = new TestLooper();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
+ // TODO(b/213280079): Start a different thread and prepare the looper, create the monitor
+ // on that thread instead of using the test main thread looper.
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
when(mContext.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE)))
.thenReturn(mSubscriptionManager);
when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE)))
.thenReturn(mTelephonyManager);
- mMonitor = new NetworkStatsSubscriptionsMonitor(mContext, mTestLooper.getLooper(),
- mExecutor, mDelegate);
+ mMonitor = new NetworkStatsSubscriptionsMonitor(mContext, mExecutor, mDelegate);
}
@Test
@@ -116,16 +123,29 @@
return list;
}
- private void setRatTypeForSub(List<RatTypeListener> listeners,
- int subId, int type) {
- final ServiceState serviceState = mock(ServiceState.class);
- when(serviceState.getDataNetworkType()).thenReturn(type);
- final RatTypeListener match = CollectionUtils
- .find(listeners, it -> it.getSubId() == subId);
+ private TelephonyDisplayInfo makeTelephonyDisplayInfo(
+ int networkType, int overrideNetworkType) {
+ // Create from parcel since final classes cannot be mocked and there is no exposed public
+ // constructors.
+ Parcel p = Parcel.obtain();
+ p.writeInt(networkType);
+ p.writeInt(overrideNetworkType);
+
+ p.setDataPosition(0);
+ return TelephonyDisplayInfo.CREATOR.createFromParcel(p);
+ }
+
+ private void setRatTypeForSub(int subId, int type) {
+ setRatTypeForSub(subId, type, OVERRIDE_NETWORK_TYPE_NONE);
+ }
+
+ private void setRatTypeForSub(int subId, int type, int overrideType) {
+ final TelephonyDisplayInfo displayInfo = makeTelephonyDisplayInfo(type, overrideType);
+ final RatTypeListener match = mRatTypeListenerOfSub.get(subId);
if (match == null) {
fail("Could not find listener with subId: " + subId);
}
- match.onServiceStateChanged(serviceState);
+ match.onDisplayInfoChanged(displayInfo);
}
private void addTestSub(int subId, String subscriberId) {
@@ -136,21 +156,47 @@
final int[] subList = convertArrayListToIntArray(mTestSubList);
when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList);
- when(mTelephonyManager.getSubscriberId(subId)).thenReturn(subscriberId);
- mMonitor.onSubscriptionsChanged();
+ updateSubscriberIdForTestSub(subId, subscriberId);
}
private void updateSubscriberIdForTestSub(int subId, @Nullable final String subscriberId) {
- when(mTelephonyManager.getSubscriberId(subId)).thenReturn(subscriberId);
+ final TelephonyManager telephonyManagerOfSub;
+ if (mTelephonyManagerOfSub.contains(subId)) {
+ telephonyManagerOfSub = mTelephonyManagerOfSub.get(subId);
+ } else {
+ telephonyManagerOfSub = mock(TelephonyManager.class);
+ mTelephonyManagerOfSub.put(subId, telephonyManagerOfSub);
+ }
+ when(telephonyManagerOfSub.getSubscriberId()).thenReturn(subscriberId);
+ when(mTelephonyManager.createForSubscriptionId(subId)).thenReturn(telephonyManagerOfSub);
mMonitor.onSubscriptionsChanged();
}
+ private void assertAndCaptureRatTypeListenerRegistration(int subId) {
+ final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
+ ArgumentCaptor.forClass(RatTypeListener.class);
+ verify(mTelephonyManagerOfSub.get(subId))
+ .registerTelephonyCallback(any(), ratTypeListenerCaptor.capture());
+ final RatTypeListener listener = CollectionUtils
+ .find(ratTypeListenerCaptor.getAllValues(), it -> it.getSubId() == subId);
+ assertNotNull(listener);
+ mRatTypeListenerOfSub.put(subId, listener);
+ }
+
private void removeTestSub(int subId) {
// Remove subId from TestSubList.
mTestSubList.removeIf(it -> it == subId);
final int[] subList = convertArrayListToIntArray(mTestSubList);
when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList);
mMonitor.onSubscriptionsChanged();
+ assertRatTypeListenerDeregistration(subId);
+ mRatTypeListenerOfSub.delete(subId);
+ mTelephonyManagerOfSub.delete(subId);
+ }
+
+ private void assertRatTypeListenerDeregistration(int subId) {
+ verify(mTelephonyManagerOfSub.get(subId))
+ .unregisterTelephonyCallback(eq(mRatTypeListenerOfSub.get(subId)));
}
private void assertRatTypeChangedForSub(String subscriberId, int ratType) {
@@ -171,9 +217,6 @@
@Test
public void testSubChangedAndRatTypeChanged() {
- final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
- ArgumentCaptor.forClass(RatTypeListener.class);
-
mMonitor.start();
// Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback
// before changing RAT type.
@@ -183,15 +226,14 @@
// Insert sim2.
addTestSub(TEST_SUBID2, TEST_IMSI2);
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
- verify(mTelephonyManager, times(2)).listen(ratTypeListenerCaptor.capture(),
- eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+ assertAndCaptureRatTypeListenerRegistration(TEST_SUBID1);
+ assertAndCaptureRatTypeListenerRegistration(TEST_SUBID2);
reset(mDelegate);
// Set RAT type of sim1 to UMTS.
// Verify RAT type of sim1 after subscription gets onCollapsedRatTypeChanged() callback
// and others remain untouched.
- setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
- TelephonyManager.NETWORK_TYPE_UMTS);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_UMTS);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN);
assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN);
@@ -200,8 +242,7 @@
// Set RAT type of sim2 to LTE.
// Verify RAT type of sim2 after subscription gets onCollapsedRatTypeChanged() callback
// and others remain untouched.
- setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID2,
- TelephonyManager.NETWORK_TYPE_LTE);
+ setRatTypeForSub(TEST_SUBID2, TelephonyManager.NETWORK_TYPE_LTE);
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_LTE);
assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN);
@@ -210,7 +251,6 @@
// Remove sim2 and verify that callbacks are fired and RAT type is correct for sim2.
// while the other two remain untouched.
removeTestSub(TEST_SUBID2);
- verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN);
assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN);
@@ -218,13 +258,12 @@
// Set RAT type of sim1 to UNKNOWN. Then stop monitoring subscription changes
// and verify that the listener for sim1 is removed.
- setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
- TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
reset(mDelegate);
mMonitor.stop();
- verify(mTelephonyManager, times(2)).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
+ assertRatTypeListenerDeregistration(TEST_SUBID1);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
}
@@ -236,104 +275,84 @@
// before changing RAT type. Also capture listener for later use.
addTestSub(TEST_SUBID1, TEST_IMSI1);
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
- final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
- ArgumentCaptor.forClass(RatTypeListener.class);
- verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
- eq(PhoneStateListener.LISTEN_SERVICE_STATE));
- final RatTypeListener listener = CollectionUtils
- .find(ratTypeListenerCaptor.getAllValues(), it -> it.getSubId() == TEST_SUBID1);
- assertNotNull(listener);
+ assertAndCaptureRatTypeListenerRegistration(TEST_SUBID1);
+ final RatTypeListener listener = mRatTypeListenerOfSub.get(TEST_SUBID1);
// Set RAT type to 5G NSA (non-standalone) mode, verify the monitor outputs
// NETWORK_TYPE_5G_NSA.
- final ServiceState serviceState = mock(ServiceState.class);
- when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
- when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED);
- listener.onServiceStateChanged(serviceState);
- assertRatTypeChangedForSub(TEST_IMSI1, NetworkTemplate.NETWORK_TYPE_5G_NSA);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_LTE,
+ OVERRIDE_NETWORK_TYPE_NR_NSA);
+ assertRatTypeChangedForSub(TEST_IMSI1, NetworkStatsManager.NETWORK_TYPE_5G_NSA);
reset(mDelegate);
// Set RAT type to LTE without NR connected, the RAT type should be downgraded to LTE.
- when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE);
- listener.onServiceStateChanged(serviceState);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_LTE,
+ OVERRIDE_NETWORK_TYPE_NONE);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE);
reset(mDelegate);
// Verify NR connected with other RAT type does not take effect.
- when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_UMTS);
- when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED);
- listener.onServiceStateChanged(serviceState);
+ // This should not be happened in practice.
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_UMTS,
+ OVERRIDE_NETWORK_TYPE_NR_NSA);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
reset(mDelegate);
// Set RAT type to 5G standalone mode, the RAT type should be NR.
- setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
- TelephonyManager.NETWORK_TYPE_NR);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_NR);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR);
reset(mDelegate);
// Set NR state to none in standalone mode does not change anything.
- when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_NR);
- when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE);
- listener.onServiceStateChanged(serviceState);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_NR, OVERRIDE_NETWORK_TYPE_NONE);
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR);
}
@Test
public void testSubscriberIdUnavailable() {
- final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
- ArgumentCaptor.forClass(RatTypeListener.class);
-
mMonitor.start();
// Insert sim1, set subscriberId to null which is normal in SIM PIN locked case.
// Verify RAT type is NETWORK_TYPE_UNKNOWN and service will not perform listener
// registration.
addTestSub(TEST_SUBID1, null);
- verify(mTelephonyManager, never()).listen(any(), anyInt());
+ verify(mTelephonyManagerOfSub.get(TEST_SUBID1), never()).listen(any(), anyInt());
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
// Set IMSI for sim1, verify the listener will be registered.
updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1);
- verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
- eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+ assertAndCaptureRatTypeListenerRegistration(TEST_SUBID1);
reset(mTelephonyManager);
- when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
// Set RAT type of sim1 to UMTS. Verify RAT type of sim1 is changed.
- setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
- TelephonyManager.NETWORK_TYPE_UMTS);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_UMTS);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
reset(mDelegate);
// Set IMSI to null again to simulate somehow IMSI is not available, such as
// modem crash. Verify service should unregister listener.
updateSubscriberIdForTestSub(TEST_SUBID1, null);
- verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()),
- eq(PhoneStateListener.LISTEN_NONE));
+ assertRatTypeListenerDeregistration(TEST_SUBID1);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
reset(mDelegate);
- clearInvocations(mTelephonyManager);
+ clearInvocations(mTelephonyManagerOfSub.get(TEST_SUBID1));
// Simulate somehow IMSI is back. Verify service will register with
// another listener and fire callback accordingly.
final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 =
ArgumentCaptor.forClass(RatTypeListener.class);
updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1);
- verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(),
- eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+ assertAndCaptureRatTypeListenerRegistration(TEST_SUBID1);
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
reset(mDelegate);
- clearInvocations(mTelephonyManager);
+ clearInvocations(mTelephonyManagerOfSub.get(TEST_SUBID1));
// Set RAT type of sim1 to LTE. Verify RAT type of sim1 still works.
- setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1,
- TelephonyManager.NETWORK_TYPE_LTE);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_LTE);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE);
reset(mDelegate);
mMonitor.stop();
- verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor2.getValue()),
- eq(PhoneStateListener.LISTEN_NONE));
+ assertRatTypeListenerDeregistration(TEST_SUBID1);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
}
@@ -349,30 +368,24 @@
// Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback
// before changing RAT type.
addTestSub(TEST_SUBID1, TEST_IMSI1);
- final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
- ArgumentCaptor.forClass(RatTypeListener.class);
- verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
- eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+ assertAndCaptureRatTypeListenerRegistration(TEST_SUBID1);
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
// Set RAT type of sim1 to UMTS.
// Verify RAT type of sim1 changes accordingly.
- setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
- TelephonyManager.NETWORK_TYPE_UMTS);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_UMTS);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
reset(mDelegate);
- clearInvocations(mTelephonyManager);
+ clearInvocations(mTelephonyManagerOfSub.get(TEST_SUBID1));
// Simulate IMSI of sim1 changed to IMSI2. Verify the service will register with
// another listener and remove the old one. The RAT type of new IMSI stays at
// NETWORK_TYPE_UNKNOWN until received initial callback from telephony.
- final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 =
- ArgumentCaptor.forClass(RatTypeListener.class);
updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI2);
- verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(),
- eq(PhoneStateListener.LISTEN_SERVICE_STATE));
- verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()),
- eq(PhoneStateListener.LISTEN_NONE));
+ final RatTypeListener oldListener = mRatTypeListenerOfSub.get(TEST_SUBID1);
+ assertAndCaptureRatTypeListenerRegistration(TEST_SUBID1);
+ verify(mTelephonyManagerOfSub.get(TEST_SUBID1), times(1))
+ .unregisterTelephonyCallback(eq(oldListener));
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN);
reset(mDelegate);
@@ -380,8 +393,7 @@
// Set RAT type of sim1 to UMTS for new listener to simulate the initial callback received
// from telephony after registration. Verify RAT type of sim1 changes with IMSI2
// accordingly.
- setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1,
- TelephonyManager.NETWORK_TYPE_UMTS);
+ setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_UMTS);
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UMTS);
reset(mDelegate);
diff --git a/tests/unit/java/com/android/server/net/TestableUsageCallback.kt b/tests/unit/java/com/android/server/net/TestableUsageCallback.kt
new file mode 100644
index 0000000..1917ec3
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/TestableUsageCallback.kt
@@ -0,0 +1,79 @@
+/*
+ * 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 com.android.server.net
+
+import android.net.DataUsageRequest
+import android.net.netstats.IUsageCallback
+import android.os.IBinder
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import kotlin.test.fail
+
+private const val DEFAULT_TIMEOUT_MS = 200L
+
+// TODO: Move the class to static libs once all downstream have IUsageCallback definition.
+class TestableUsageCallback(private val binder: IBinder) : IUsageCallback.Stub() {
+ sealed class CallbackType(val request: DataUsageRequest) {
+ class OnThresholdReached(request: DataUsageRequest) : CallbackType(request)
+ class OnCallbackReleased(request: DataUsageRequest) : CallbackType(request)
+ }
+
+ // TODO: Change to use ArrayTrackRecord once moved into to the module.
+ private val history = LinkedBlockingQueue<CallbackType>()
+
+ override fun onThresholdReached(request: DataUsageRequest) {
+ history.add(CallbackType.OnThresholdReached(request))
+ }
+
+ override fun onCallbackReleased(request: DataUsageRequest) {
+ history.add(CallbackType.OnCallbackReleased(request))
+ }
+
+ fun expectOnThresholdReached(request: DataUsageRequest) {
+ expectCallback<CallbackType.OnThresholdReached>(request, DEFAULT_TIMEOUT_MS)
+ }
+
+ fun expectOnCallbackReleased(request: DataUsageRequest) {
+ expectCallback<CallbackType.OnCallbackReleased>(request, DEFAULT_TIMEOUT_MS)
+ }
+
+ @JvmOverloads
+ fun assertNoCallback(timeout: Long = DEFAULT_TIMEOUT_MS) {
+ val cb = history.poll(timeout, TimeUnit.MILLISECONDS)
+ cb?.let { fail("Expected no callback but got $cb") }
+ }
+
+ // Expects a callback of the specified request on the specified network within the timeout.
+ // If no callback arrives, or a different callback arrives, fail.
+ private inline fun <reified T : CallbackType> expectCallback(
+ expectedRequest: DataUsageRequest,
+ timeoutMs: Long
+ ) {
+ history.poll(timeoutMs, TimeUnit.MILLISECONDS).let {
+ if (it !is T || it.request != expectedRequest) {
+ fail("Unexpected callback : $it," +
+ " expected ${T::class} with Request[$expectedRequest]")
+ } else {
+ it
+ }
+ }
+ }
+
+ override fun asBinder(): IBinder {
+ return binder
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index fe971e7..0f71c13 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -24,7 +24,7 @@
"libbpf_android",
"liblog",
"libnativehelper",
- "libnetdbpf",
"libnetdutils",
+ "libnetworkstats",
],
}
diff --git a/tests/unit/res/raw/netstats_uid_v16 b/tests/unit/res/raw/netstats_uid_v16
new file mode 100644
index 0000000..a6ee430
--- /dev/null
+++ b/tests/unit/res/raw/netstats_uid_v16
Binary files differ