Merge changes Ifd6be50a,I857e40c9,I1b9f4fde,Ib3b43cf2
* changes:
Prevent native_init from starting TrafficController
Remove libutils dependency from libservice-connectivity
Merge libtraffic_controller_jni into libservice-connectivity
[NETD-TC#15] Make ConnectivityService and PermissionMonitor calls BpfNetMaps on T
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 780ba26..302c0b3 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,9 @@
]
},
{
+ "name": "netd_updatable_unit_test"
+ },
+ {
"name": "TetheringTests"
},
{
@@ -40,6 +43,10 @@
"name": "bpf_existence_test"
},
{
+ "name": "netd_updatable_unit_test",
+ "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
+ },
+ {
"name": "libclat_test"
},
{
@@ -63,6 +70,9 @@
]
},
{
+ "name": "netd_updatable_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ },
+ {
"name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index c0f68fd..721f05e 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -54,6 +54,7 @@
"libservice-connectivity",
"libcom_android_connectivity_com_android_net_module_util_jni",
],
+ native_shared_libs: ["libnetd_updatable"],
},
both: {
jni_libs: ["libframework-connectivity-jni"],
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index cce2b71..6718402 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -42,6 +42,7 @@
// 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/netd",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service/native/libs/libclat",
"//packages/modules/Connectivity/Tethering",
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 8da421d..5cfab5d 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -132,6 +132,7 @@
}
public final class NetworkCapabilities implements android.os.Parcelable {
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public java.util.Set<java.lang.Integer> getAccessUids();
method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
method public boolean hasForbiddenCapability(int);
field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
@@ -143,6 +144,7 @@
}
public static final class NetworkCapabilities.Builder {
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAccessUids(@NonNull java.util.Set<java.lang.Integer>);
method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 4ae3a06..4254a66 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -264,6 +264,7 @@
mTransportInfo = null;
mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
mUids = null;
+ mAccessUids.clear();
mAdministratorUids = new int[0];
mOwnerUid = Process.INVALID_UID;
mSSID = null;
@@ -294,6 +295,7 @@
}
mSignalStrength = nc.mSignalStrength;
mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids);
+ setAccessUids(nc.mAccessUids);
setAdministratorUids(nc.getAdministratorUids());
mOwnerUid = nc.mOwnerUid;
mForbiddenNetworkCapabilities = nc.mForbiddenNetworkCapabilities;
@@ -1025,6 +1027,7 @@
final int[] originalAdministratorUids = getAdministratorUids();
final TransportInfo originalTransportInfo = getTransportInfo();
final Set<Integer> originalSubIds = getSubscriptionIds();
+ final Set<Integer> originalAccessUids = new ArraySet<>(mAccessUids);
clearAll();
if (0 != (originalCapabilities & (1 << NET_CAPABILITY_NOT_RESTRICTED))) {
// If the test network is not restricted, then it is only allowed to declare some
@@ -1044,6 +1047,7 @@
mNetworkSpecifier = originalSpecifier;
mSignalStrength = originalSignalStrength;
mTransportInfo = originalTransportInfo;
+ mAccessUids.addAll(originalAccessUids);
// Only retain the owner and administrator UIDs if they match the app registering the remote
// caller that registered the network.
@@ -1809,6 +1813,86 @@
}
/**
+ * List of UIDs that can always access this network.
+ * <p>
+ * UIDs in this list have access to this network, even if the network doesn't have the
+ * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+ * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+ * This is only useful for restricted networks. For non-restricted networks it has no effect.
+ * <p>
+ * This is disallowed in {@link NetworkRequest}, and can only be set by network agents. Network
+ * agents also have restrictions on how they can set these ; they can only back a public
+ * Android API. As such, Ethernet agents can set this when backing the per-UID access API, and
+ * Telephony can set exactly one UID which has to match the manager app for the associated
+ * subscription. Failure to comply with these rules will see this member cleared.
+ * <p>
+ * This member is never null, but can be empty.
+ * @hide
+ */
+ @NonNull
+ private final ArraySet<Integer> mAccessUids = new ArraySet<>();
+
+ /**
+ * Set the list of UIDs that can always access this network.
+ * @param uids
+ * @hide
+ */
+ public void setAccessUids(@NonNull final Set<Integer> uids) {
+ // could happen with nc.set(nc), cheaper than always making a defensive copy
+ if (uids == mAccessUids) return;
+
+ Objects.requireNonNull(uids);
+ mAccessUids.clear();
+ mAccessUids.addAll(uids);
+ }
+
+ /**
+ * The list of UIDs that can always access this network.
+ *
+ * The UIDs in this list can always access this network, even if it is restricted and
+ * the UID doesn't hold the USE_RESTRICTED_NETWORKS permission. This is defined by the
+ * network agent in charge of creating the network.
+ *
+ * The UIDs are only visible to network factories and the system server, since the system
+ * server makes sure to redact them before sending a NetworkCapabilities to a process
+ * that doesn't hold the permission.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public @NonNull Set<Integer> getAccessUids() {
+ return new ArraySet<>(mAccessUids);
+ }
+
+ /** @hide */
+ // For internal clients that know what they are doing and need to avoid the performance hit
+ // of the defensive copy.
+ public @NonNull ArraySet<Integer> getAccessUidsNoCopy() {
+ return mAccessUids;
+ }
+
+ /**
+ * Test whether this UID has special permission to access this network, as per mAccessUids.
+ * @hide
+ */
+ public boolean isAccessUid(int uid) {
+ return mAccessUids.contains(uid);
+ }
+
+ /**
+ * @return whether any UID is in the list of access UIDs
+ * @hide
+ */
+ public boolean hasAccessUids() {
+ return !mAccessUids.isEmpty();
+ }
+
+ private boolean equalsAccessUids(@NonNull NetworkCapabilities other) {
+ return mAccessUids.equals(other.mAccessUids);
+ }
+
+ /**
* The SSID of the network, or null if not applicable or unknown.
* <p>
* This is filled in by wifi code.
@@ -1962,6 +2046,7 @@
&& equalsSpecifier(that)
&& equalsTransportInfo(that)
&& equalsUids(that)
+ && equalsAccessUids(that)
&& equalsSSID(that)
&& equalsOwnerUid(that)
&& equalsPrivateDnsBroken(that)
@@ -1986,15 +2071,16 @@
+ mSignalStrength * 29
+ mOwnerUid * 31
+ Objects.hashCode(mUids) * 37
- + Objects.hashCode(mSSID) * 41
- + Objects.hashCode(mTransportInfo) * 43
- + Objects.hashCode(mPrivateDnsBroken) * 47
- + Objects.hashCode(mRequestorUid) * 53
- + Objects.hashCode(mRequestorPackageName) * 59
- + Arrays.hashCode(mAdministratorUids) * 61
- + Objects.hashCode(mSubIds) * 67
- + Objects.hashCode(mUnderlyingNetworks) * 71
- + mEnterpriseId * 73;
+ + Objects.hashCode(mAccessUids) * 41
+ + Objects.hashCode(mSSID) * 43
+ + Objects.hashCode(mTransportInfo) * 47
+ + Objects.hashCode(mPrivateDnsBroken) * 53
+ + Objects.hashCode(mRequestorUid) * 59
+ + Objects.hashCode(mRequestorPackageName) * 61
+ + Arrays.hashCode(mAdministratorUids) * 67
+ + Objects.hashCode(mSubIds) * 71
+ + Objects.hashCode(mUnderlyingNetworks) * 73
+ + mEnterpriseId * 79;
}
@Override
@@ -2022,6 +2108,7 @@
dest.writeParcelable((Parcelable) mTransportInfo, flags);
dest.writeInt(mSignalStrength);
writeParcelableArraySet(dest, mUids, flags);
+ dest.writeIntArray(CollectionUtils.toIntArray(mAccessUids));
dest.writeString(mSSID);
dest.writeBoolean(mPrivateDnsBroken);
dest.writeIntArray(getAdministratorUids());
@@ -2034,7 +2121,7 @@
}
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
- new Creator<NetworkCapabilities>() {
+ new Creator<>() {
@Override
public NetworkCapabilities createFromParcel(Parcel in) {
NetworkCapabilities netCap = new NetworkCapabilities();
@@ -2048,6 +2135,11 @@
netCap.mTransportInfo = in.readParcelable(null);
netCap.mSignalStrength = in.readInt();
netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */);
+ final int[] accessUids = in.createIntArray();
+ netCap.mAccessUids.ensureCapacity(accessUids.length);
+ for (int uid : accessUids) {
+ netCap.mAccessUids.add(uid);
+ }
netCap.mSSID = in.readString();
netCap.mPrivateDnsBroken = in.readBoolean();
netCap.setAdministratorUids(in.createIntArray());
@@ -2124,6 +2216,11 @@
sb.append(" Uids: <").append(mUids).append(">");
}
}
+
+ if (hasAccessUids()) {
+ sb.append(" AccessUids: <").append(mAccessUids).append(">");
+ }
+
if (mOwnerUid != Process.INVALID_UID) {
sb.append(" OwnerUid: ").append(mOwnerUid);
}
@@ -2300,7 +2397,7 @@
private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
if (!isValidCapability(capability)) {
- throw new IllegalArgumentException("NetworkCapability " + capability + "out of range");
+ throw new IllegalArgumentException("NetworkCapability " + capability + " out of range");
}
}
@@ -2909,6 +3006,44 @@
}
/**
+ * Set a list of UIDs that can always access this network
+ * <p>
+ * Provide a list of UIDs that can access this network even if the network doesn't have the
+ * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+ * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+ * <p>
+ * This is disallowed in {@link NetworkRequest}, and can only be set by
+ * {@link NetworkAgent}s, who hold the
+ * {@link android.Manifest.permission.NETWORK_FACTORY} permission.
+ * Network agents also have restrictions on how they can set these ; they can only back
+ * a public Android API. As such, Ethernet agents can set this when backing the per-UID
+ * access API, and Telephony can set exactly one UID which has to match the manager app for
+ * the associated subscription. Failure to comply with these rules will see this member
+ * cleared.
+ * <p>
+ * These UIDs are only visible to network factories and the system server, since the system
+ * server makes sure to redact them before sending a {@link NetworkCapabilities} instance
+ * to a process that doesn't hold the {@link android.Manifest.permission.NETWORK_FACTORY}
+ * permission.
+ * <p>
+ * This list cannot be null, but it can be empty to mean that no UID without the
+ * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission
+ * gets to access this network.
+ *
+ * @param uids the list of UIDs that can always access this network
+ * @return this builder
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public Builder setAccessUids(@NonNull Set<Integer> uids) {
+ Objects.requireNonNull(uids);
+ mCaps.setAccessUids(uids);
+ return this;
+ }
+
+ /**
* Set the underlying networks of this network.
*
* @param networks The underlying networks of this network.
diff --git a/netd/Android.bp b/netd/Android.bp
new file mode 100644
index 0000000..53c1bd4
--- /dev/null
+++ b/netd/Android.bp
@@ -0,0 +1,81 @@
+//
+// 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: "libnetd_updatable",
+ version_script: "libnetd_updatable.map.txt",
+ stubs: {
+ versions: [
+ "1",
+ ],
+ symbol_file: "libnetd_updatable.map.txt",
+ },
+ defaults: ["netd_defaults"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ "libcutils_headers",
+ ],
+ srcs: [
+ "BpfHandler.cpp",
+ "NetdUpdatable.cpp",
+ ],
+ static_libs: [
+ "libnetdutils",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ export_include_dirs: ["include"],
+ header_abi_checker: {
+ enabled: true,
+ symbol_file: "libnetd_updatable.map.txt",
+ },
+ sanitize: {
+ cfi: true,
+ },
+ apex_available: ["com.android.tethering"],
+ min_sdk_version: "30",
+}
+
+cc_test {
+ name: "netd_updatable_unit_test",
+ defaults: ["netd_defaults"],
+ test_suites: ["general-tests"],
+ require_root: true, // required by setrlimitForTest()
+ header_libs: [
+ "bpf_connectivity_headers",
+ ],
+ srcs: [
+ "BpfHandlerTest.cpp",
+ ],
+ static_libs: [
+ "libnetd_updatable",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ "libnetdutils",
+ ],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+}
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
new file mode 100644
index 0000000..3cd5e13
--- /dev/null
+++ b/netd/BpfHandler.cpp
@@ -0,0 +1,212 @@
+/**
+ * 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 "BpfHandler"
+
+#include "BpfHandler.h"
+
+#include <linux/bpf.h>
+
+#include <android-base/unique_fd.h>
+#include <bpf/WaitForProgsLoaded.h>
+#include <log/log.h>
+#include <netdutils/UidConstants.h>
+#include <private/android_filesystem_config.h>
+
+#include "BpfSyscallWrappers.h"
+
+namespace android {
+namespace net {
+
+using base::unique_fd;
+using bpf::NONEXISTENT_COOKIE;
+using bpf::getSocketCookie;
+using bpf::retrieveProgram;
+using netdutils::Status;
+using netdutils::statusFromErrno;
+
+constexpr int PER_UID_STATS_ENTRIES_LIMIT = 500;
+// At most 90% of the stats map may be used by tagged traffic entries. This ensures
+// that 10% of the map is always available to count untagged traffic, one entry per UID.
+// Otherwise, apps would be able to avoid data usage accounting entirely by filling up the
+// map with tagged traffic entries.
+constexpr int TOTAL_UID_STATS_ENTRIES_LIMIT = STATS_MAP_SIZE * 0.9;
+
+static_assert(STATS_MAP_SIZE - TOTAL_UID_STATS_ENTRIES_LIMIT > 100,
+ "The limit for stats map is to high, stats data may be lost due to overflow");
+
+static Status attachProgramToCgroup(const char* programPath, const unique_fd& cgroupFd,
+ bpf_attach_type type) {
+ unique_fd cgroupProg(retrieveProgram(programPath));
+ if (cgroupProg == -1) {
+ int ret = errno;
+ ALOGE("Failed to get program from %s: %s", programPath, strerror(ret));
+ return statusFromErrno(ret, "cgroup program get failed");
+ }
+ if (android::bpf::attachProgram(type, cgroupProg, cgroupFd)) {
+ int ret = errno;
+ ALOGE("Program from %s attach failed: %s", programPath, strerror(ret));
+ return statusFromErrno(ret, "program attach failed");
+ }
+ return netdutils::status::ok;
+}
+
+static Status initPrograms(const char* cg2_path) {
+ unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+ if (cg_fd == -1) {
+ int ret = errno;
+ ALOGE("Failed to open the cgroup directory: %s", strerror(ret));
+ return statusFromErrno(ret, "Open the cgroup directory failed");
+ }
+ RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
+
+ // For the devices that support cgroup socket filter, the socket filter
+ // should be loaded successfully by bpfloader. So we attach the filter to
+ // cgroup if the program is pinned properly.
+ // TODO: delete the if statement once all devices should support cgroup
+ // socket filter (ie. the minimum kernel version required is 4.14).
+ if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
+ RETURN_IF_NOT_OK(
+ attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+ }
+ return netdutils::status::ok;
+}
+
+BpfHandler::BpfHandler()
+ : mPerUidStatsEntriesLimit(PER_UID_STATS_ENTRIES_LIMIT),
+ mTotalUidStatsEntriesLimit(TOTAL_UID_STATS_ENTRIES_LIMIT) {}
+
+BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
+ : mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+
+Status BpfHandler::init(const char* cg2_path) {
+ // Make sure BPF programs are loaded before doing anything
+ android::bpf::waitForProgsLoaded();
+ ALOGI("BPF programs are loaded");
+
+ RETURN_IF_NOT_OK(initPrograms(cg2_path));
+ RETURN_IF_NOT_OK(initMaps());
+
+ return netdutils::status::ok;
+}
+
+Status BpfHandler::initMaps() {
+ std::lock_guard guard(mMutex);
+ RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_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(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
+ RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
+ BPF_ANY));
+ RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+
+ return netdutils::status::ok;
+}
+
+bool BpfHandler::hasUpdateDeviceStatsPermission(uid_t uid) {
+ // This implementation is the same logic as method ActivityManager#checkComponentPermission.
+ // It implies that the real uid can never be the same as PER_USER_RANGE.
+ uint32_t appId = uid % PER_USER_RANGE;
+ auto permission = mUidPermissionMap.readValue(appId);
+ if (permission.ok() && (permission.value() & BPF_PERMISSION_UPDATE_DEVICE_STATS)) {
+ return true;
+ }
+ return ((appId == AID_ROOT) || (appId == AID_SYSTEM) || (appId == AID_DNS));
+}
+
+int BpfHandler::tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
+ std::lock_guard guard(mMutex);
+ if (chargeUid != realUid && !hasUpdateDeviceStatsPermission(realUid)) {
+ return -EPERM;
+ }
+
+ uint64_t sock_cookie = getSocketCookie(sockFd);
+ if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+ UidTagValue newKey = {.uid = (uint32_t)chargeUid, .tag = tag};
+
+ uint32_t totalEntryCount = 0;
+ uint32_t perUidEntryCount = 0;
+ // Now we go through the stats map and count how many entries are associated
+ // with chargeUid. If the uid entry hit the limit for each chargeUid, we block
+ // the request to prevent the map from overflow. It is safe here to iterate
+ // over the map since when mMutex is hold, system server cannot toggle
+ // the live stats map and clean it. So nobody can delete entries from the map.
+ const auto countUidStatsEntries = [chargeUid, &totalEntryCount, &perUidEntryCount](
+ const StatsKey& key,
+ const BpfMap<StatsKey, StatsValue>&) {
+ if (key.uid == chargeUid) {
+ perUidEntryCount++;
+ }
+ totalEntryCount++;
+ return base::Result<void>();
+ };
+ auto configuration = mConfigurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
+ if (!configuration.ok()) {
+ ALOGE("Failed to get current configuration: %s, fd: %d",
+ strerror(configuration.error().code()), mConfigurationMap.getMap().get());
+ return -configuration.error().code();
+ }
+ if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+ ALOGE("unknown configuration value: %d", configuration.value());
+ return -EINVAL;
+ }
+
+ BpfMap<StatsKey, StatsValue>& currentMap =
+ (configuration.value() == SELECT_MAP_A) ? mStatsMapA : mStatsMapB;
+ base::Result<void> res = currentMap.iterate(countUidStatsEntries);
+ if (!res.ok()) {
+ ALOGE("Failed to count the stats entry in map %d: %s", currentMap.getMap().get(),
+ strerror(res.error().code()));
+ return -res.error().code();
+ }
+
+ if (totalEntryCount > mTotalUidStatsEntriesLimit ||
+ perUidEntryCount > mPerUidStatsEntriesLimit) {
+ ALOGE("Too many stats entries in the map, total count: %u, chargeUid(%u) count: %u,"
+ " blocking tag request to prevent map overflow",
+ totalEntryCount, chargeUid, perUidEntryCount);
+ return -EMFILE;
+ }
+ // Update the tag information of a socket to the cookieUidMap. Use BPF_ANY
+ // flag so it will insert a new entry to the map if that value doesn't exist
+ // yet. And update the tag if there is already a tag stored. Since the eBPF
+ // program in kernel only read this map, and is protected by rcu read lock. It
+ // should be fine to cocurrently update the map while eBPF program is running.
+ res = mCookieTagMap.writeValue(sock_cookie, newKey, BPF_ANY);
+ if (!res.ok()) {
+ ALOGE("Failed to tag the socket: %s, fd: %d", strerror(res.error().code()),
+ mCookieTagMap.getMap().get());
+ return -res.error().code();
+ }
+ return 0;
+}
+
+int BpfHandler::untagSocket(int sockFd) {
+ std::lock_guard guard(mMutex);
+ uint64_t sock_cookie = getSocketCookie(sockFd);
+
+ if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+ base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
+ if (!res.ok()) {
+ ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
+ return -res.error().code();
+ }
+ return 0;
+}
+
+} // namespace net
+} // namespace android
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
new file mode 100644
index 0000000..2ede1c1
--- /dev/null
+++ b/netd/BpfHandler.h
@@ -0,0 +1,83 @@
+/**
+ * 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 <mutex>
+
+#include <netdutils/Status.h>
+#include "bpf/BpfMap.h"
+#include "bpf_shared.h"
+
+using android::bpf::BpfMap;
+
+namespace android {
+namespace net {
+
+class BpfHandler {
+ public:
+ BpfHandler();
+ BpfHandler(const BpfHandler&) = delete;
+ BpfHandler& operator=(const BpfHandler&) = delete;
+ netdutils::Status init(const char* cg2_path);
+ /*
+ * Tag the socket with the specified tag and uid. In the qtaguid module, the
+ * first tag request that grab the spinlock of rb_tree can update the tag
+ * information first and other request need to wait until it finish. All the
+ * tag request will be addressed in the order of they obtaining the spinlock.
+ * In the eBPF implementation, the kernel will try to update the eBPF map
+ * entry with the tag request. And the hashmap update process is protected by
+ * the spinlock initialized with the map. So the behavior of two modules
+ * should be the same. No additional lock needed.
+ */
+ int tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid);
+
+ /*
+ * The untag process is similar to tag socket and both old qtaguid module and
+ * new eBPF module have spinlock inside the kernel for concurrent update. No
+ * external lock is required.
+ */
+ int untagSocket(int sockFd);
+
+ private:
+ // For testing
+ BpfHandler(uint32_t perUidLimit, uint32_t totalLimit);
+
+ netdutils::Status initMaps();
+ bool hasUpdateDeviceStatsPermission(uid_t uid);
+
+ BpfMap<uint64_t, UidTagValue> mCookieTagMap;
+ BpfMap<StatsKey, StatsValue> mStatsMapA;
+ BpfMap<StatsKey, StatsValue> mStatsMapB;
+ BpfMap<uint32_t, uint8_t> mConfigurationMap;
+ BpfMap<uint32_t, uint8_t> mUidPermissionMap;
+
+ std::mutex mMutex;
+
+ // The limit on the number of stats entries a uid can have in the per uid stats map. BpfHandler
+ // will block that specific uid from tagging new sockets after the limit is reached.
+ const uint32_t mPerUidStatsEntriesLimit;
+
+ // The limit on the total number of stats entries in the per uid stats map. BpfHandler will
+ // block all tagging requests after the limit is reached.
+ const uint32_t mTotalUidStatsEntriesLimit;
+
+ // For testing
+ friend class BpfHandlerTest;
+};
+
+} // namespace net
+} // namespace android
\ No newline at end of file
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
new file mode 100644
index 0000000..db59c7c
--- /dev/null
+++ b/netd/BpfHandlerTest.cpp
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ *
+ * BpfHandlerTest.cpp - unit tests for BpfHandler.cpp
+ */
+
+#include <sys/socket.h>
+
+#include <gtest/gtest.h>
+
+#include "BpfHandler.h"
+
+using namespace android::bpf; // NOLINT(google-build-using-namespace): exempted
+
+namespace android {
+namespace net {
+
+using base::Result;
+
+constexpr int TEST_MAP_SIZE = 10;
+constexpr int TEST_COOKIE = 1;
+constexpr uid_t TEST_UID = 10086;
+constexpr uid_t TEST_UID2 = 54321;
+constexpr uint32_t TEST_TAG = 42;
+constexpr uint32_t TEST_COUNTERSET = 1;
+constexpr uint32_t TEST_PER_UID_STATS_ENTRIES_LIMIT = 3;
+constexpr uint32_t TEST_TOTAL_UID_STATS_ENTRIES_LIMIT = 7;
+
+#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
+
+class BpfHandlerTest : public ::testing::Test {
+ protected:
+ BpfHandlerTest()
+ : mBh(TEST_PER_UID_STATS_ENTRIES_LIMIT, TEST_TOTAL_UID_STATS_ENTRIES_LIMIT) {}
+ BpfHandler mBh;
+ BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
+ BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
+ BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+ BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+
+ void SetUp() {
+ std::lock_guard guard(mBh.mMutex);
+ ASSERT_EQ(0, setrlimitForTest());
+
+ mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
+ TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeCookieTagMap);
+
+ 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);
+
+ mFakeUidPermissionMap.reset(
+ createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeUidPermissionMap);
+
+ mBh.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+ ASSERT_VALID(mBh.mCookieTagMap);
+ mBh.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+ ASSERT_VALID(mBh.mStatsMapA);
+ mBh.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+ ASSERT_VALID(mBh.mConfigurationMap);
+ // Always write to stats map A by default.
+ ASSERT_RESULT_OK(mBh.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
+ SELECT_MAP_A, BPF_ANY));
+ mBh.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+ ASSERT_VALID(mBh.mUidPermissionMap);
+ }
+
+ int dupFd(const android::base::unique_fd& mapFd) {
+ return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
+ }
+
+ int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid,
+ uid_t realUid) {
+ int sock = socket(protocol, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ EXPECT_LE(0, sock);
+ *cookie = getSocketCookie(sock);
+ EXPECT_NE(NONEXISTENT_COOKIE, *cookie);
+ EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, realUid));
+ return sock;
+ }
+
+ void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) {
+ Result<UidTagValue> tagResult = mFakeCookieTagMap.readValue(cookie);
+ ASSERT_RESULT_OK(tagResult);
+ EXPECT_EQ(uid, tagResult.value().uid);
+ EXPECT_EQ(tag, tagResult.value().tag);
+ }
+
+ void expectNoTag(uint64_t cookie) { EXPECT_FALSE(mFakeCookieTagMap.readValue(cookie).ok()); }
+
+ 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};
+ EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+ key->tag = 0;
+ EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+ // put tag information back to statsKey
+ key->tag = tag;
+ }
+
+ template <class Key, class Value>
+ void expectMapEmpty(BpfMap<Key, Value>& map) {
+ auto isEmpty = map.isEmpty();
+ EXPECT_RESULT_OK(isEmpty);
+ EXPECT_TRUE(isEmpty.value());
+ }
+
+ void expectTagSocketReachLimit(uint32_t tag, uint32_t uid) {
+ int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ EXPECT_LE(0, sock);
+ if (sock < 0) return;
+ uint64_t sockCookie = getSocketCookie(sock);
+ EXPECT_NE(NONEXISTENT_COOKIE, sockCookie);
+ EXPECT_EQ(-EMFILE, mBh.tagSocket(sock, tag, uid, uid));
+ expectNoTag(sockCookie);
+
+ // Delete stats entries then tag socket success
+ StatsKey key = {.uid = uid, .tag = 0, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
+ ASSERT_RESULT_OK(mFakeStatsMapA.deleteValue(key));
+ EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, uid));
+ expectUidTag(sockCookie, uid, tag);
+ }
+};
+
+TEST_F(BpfHandlerTest, TestTagSocketV4) {
+ uint64_t sockCookie;
+ int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+ expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+ ASSERT_EQ(0, mBh.untagSocket(v4socket));
+ expectNoTag(sockCookie);
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestReTagSocket) {
+ uint64_t sockCookie;
+ int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+ expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+ ASSERT_EQ(0, mBh.tagSocket(v4socket, TEST_TAG + 1, TEST_UID + 1, TEST_UID + 1));
+ expectUidTag(sockCookie, TEST_UID + 1, TEST_TAG + 1);
+}
+
+TEST_F(BpfHandlerTest, TestTagTwoSockets) {
+ uint64_t sockCookie1;
+ uint64_t sockCookie2;
+ int v4socket1 = setUpSocketAndTag(AF_INET, &sockCookie1, TEST_TAG, TEST_UID, TEST_UID);
+ setUpSocketAndTag(AF_INET, &sockCookie2, TEST_TAG, TEST_UID, TEST_UID);
+ expectUidTag(sockCookie1, TEST_UID, TEST_TAG);
+ expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
+ ASSERT_EQ(0, mBh.untagSocket(v4socket1));
+ expectNoTag(sockCookie1);
+ expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
+ ASSERT_FALSE(mFakeCookieTagMap.getNextKey(sockCookie2).ok());
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketV6) {
+ uint64_t sockCookie;
+ int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+ expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+ ASSERT_EQ(0, mBh.untagSocket(v6socket));
+ expectNoTag(sockCookie);
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagInvalidSocket) {
+ int invalidSocket = -1;
+ ASSERT_GT(0, mBh.tagSocket(invalidSocket, TEST_TAG, TEST_UID, TEST_UID));
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketWithoutPermission) {
+ int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_NE(-1, sock);
+ ASSERT_EQ(-EPERM, mBh.tagSocket(sock, TEST_TAG, TEST_UID, TEST_UID2));
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketWithPermission) {
+ // Grant permission to real uid. In practice, the uid permission map will be updated by
+ // TrafficController::setPermissionForUids().
+ uid_t realUid = TEST_UID2;
+ ASSERT_RESULT_OK(mFakeUidPermissionMap.writeValue(realUid,
+ BPF_PERMISSION_UPDATE_DEVICE_STATS, BPF_ANY));
+
+ // Tag a socket to a different uid other then realUid.
+ uint64_t sockCookie;
+ int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, realUid);
+ expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+ EXPECT_EQ(0, mBh.untagSocket(v6socket));
+ expectNoTag(sockCookie);
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestUntagInvalidSocket) {
+ int invalidSocket = -1;
+ ASSERT_GT(0, mBh.untagSocket(invalidSocket));
+ int v4socket = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_GT(0, mBh.untagSocket(v4socket));
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketReachLimitFail) {
+ uid_t uid = TEST_UID;
+ StatsKey tagStatsMapKey[3];
+ for (int i = 0; i < 3; i++) {
+ uint64_t cookie = TEST_COOKIE + i;
+ uint32_t tag = TEST_TAG + i;
+ populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+ }
+ expectTagSocketReachLimit(TEST_TAG, TEST_UID);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketReachTotalLimitFail) {
+ StatsKey tagStatsMapKey[4];
+ for (int i = 0; i < 4; i++) {
+ uint64_t cookie = TEST_COOKIE + i;
+ uint32_t tag = TEST_TAG + i;
+ uid_t uid = TEST_UID + i;
+ populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+ }
+ expectTagSocketReachLimit(TEST_TAG, TEST_UID);
+}
+
+} // namespace net
+} // namespace android
diff --git a/netd/NetdUpdatable.cpp b/netd/NetdUpdatable.cpp
new file mode 100644
index 0000000..f0997fc
--- /dev/null
+++ b/netd/NetdUpdatable.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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 "NetdUpdatable"
+
+#include "NetdUpdatable.h"
+
+#include <android-base/logging.h>
+#include <netdutils/Status.h>
+
+#include "NetdUpdatablePublic.h"
+
+int libnetd_updatable_init(const char* cg2_path) {
+ android::base::InitLogging(/*argv=*/nullptr);
+ LOG(INFO) << __func__ << ": Initializing";
+
+ android::net::gNetdUpdatable = android::net::NetdUpdatable::getInstance();
+ android::netdutils::Status ret = android::net::gNetdUpdatable->mBpfHandler.init(cg2_path);
+ if (!android::netdutils::isOk(ret)) {
+ LOG(ERROR) << __func__ << ": BPF handler init failed";
+ return -ret.code();
+ }
+ return 0;
+}
+
+int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
+ if (android::net::gNetdUpdatable == nullptr) return -EPERM;
+ return android::net::gNetdUpdatable->mBpfHandler.tagSocket(sockFd, tag, chargeUid, realUid);
+}
+
+int libnetd_updatable_untagSocket(int sockFd) {
+ if (android::net::gNetdUpdatable == nullptr) return -EPERM;
+ return android::net::gNetdUpdatable->mBpfHandler.untagSocket(sockFd);
+}
+
+namespace android {
+namespace net {
+
+NetdUpdatable* gNetdUpdatable = nullptr;
+
+NetdUpdatable* NetdUpdatable::getInstance() {
+ // Instantiated on first use.
+ static NetdUpdatable instance;
+ return &instance;
+}
+
+} // namespace net
+} // namespace android
diff --git a/netd/NetdUpdatable.h b/netd/NetdUpdatable.h
new file mode 100644
index 0000000..333037f
--- /dev/null
+++ b/netd/NetdUpdatable.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 "BpfHandler.h"
+
+namespace android {
+namespace net {
+
+class NetdUpdatable {
+ public:
+ NetdUpdatable() = default;
+ NetdUpdatable(const NetdUpdatable&) = delete;
+ NetdUpdatable& operator=(const NetdUpdatable&) = delete;
+ static NetdUpdatable* getInstance();
+
+ BpfHandler mBpfHandler;
+};
+
+extern NetdUpdatable* gNetdUpdatable;
+
+} // namespace net
+} // namespace android
\ No newline at end of file
diff --git a/netd/include/NetdUpdatablePublic.h b/netd/include/NetdUpdatablePublic.h
new file mode 100644
index 0000000..1ca5ea2
--- /dev/null
+++ b/netd/include/NetdUpdatablePublic.h
@@ -0,0 +1,61 @@
+/*
+ * 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 <stdint.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+/*
+ * Initial function for libnetd_updatable library.
+ *
+ * The function uses |cg2_path| as cgroup v2 mount location to attach BPF programs so that the
+ * kernel can record packet number, size, etc. in BPF maps when packets pass through, and let user
+ * space retrieve statistics.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on
+ * failure.
+ */
+int libnetd_updatable_init(const char* cg2_path);
+
+/*
+ * Set the socket tag and owning UID for traffic statistics on the specified socket. Permission
+ * check is performed based on the |realUid| before socket tagging.
+ *
+ * The |sockFd| is a file descriptor of the socket that needs to tag. The |tag| is the mark to tag.
+ * It can be an arbitrary value in uint32_t range. The |chargeUid| is owning uid which will be
+ * tagged along with the |tag|. The |realUid| is an effective uid of the calling process, which is
+ * used for permission check before socket tagging.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
+ */
+int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid,
+ uid_t realUid);
+
+/*
+ * Untag a network socket. Future traffic on this socket will no longer be associated with any
+ * previously configured tag and uid.
+ *
+ * The |sockFd| is a file descriptor of the socket that wants to untag.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
+ */
+int libnetd_updatable_untagSocket(int sockFd);
+
+__END_DECLS
\ No newline at end of file
diff --git a/netd/libnetd_updatable.map.txt b/netd/libnetd_updatable.map.txt
new file mode 100644
index 0000000..dcb11a1
--- /dev/null
+++ b/netd/libnetd_updatable.map.txt
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This lists the entry points visible to applications that use the libnetd_updatable
+# library. Other entry points present in the library won't be usable.
+
+LIBNETD_UPDATABLE {
+ global:
+ libnetd_updatable_init; # apex
+ libnetd_updatable_tagSocket; # apex
+ libnetd_updatable_untagSocket; # apex
+ local:
+ *;
+};
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 0259f7d..a453270 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -249,6 +249,7 @@
import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.netlink.InetDiagMessage;
import com.android.server.connectivity.AutodestructReference;
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
@@ -439,8 +440,6 @@
* Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
* The default request uses PREFERENCE_ORDER_DEFAULT.
*/
- // Bound for the lowest valid preference order.
- static final int PREFERENCE_ORDER_LOWEST = 999;
// Used when sending to netd to code for "no order".
static final int PREFERENCE_ORDER_NONE = 0;
// Order for requests that don't code for a per-app preference. As it is
@@ -448,11 +447,6 @@
// PREFERENCE_ORDER_NONE when sending to netd.
@VisibleForTesting
static final int PREFERENCE_ORDER_INVALID = Integer.MAX_VALUE;
- // Order for the default internet request. Since this must always have the
- // lowest priority, its value is larger than the largest acceptable value. As
- // it is out of the valid range, the corresponding order should be
- // PREFERENCE_ORDER_NONE when sending to netd.
- static final int PREFERENCE_ORDER_DEFAULT = 1000;
// As a security feature, VPNs have the top priority.
static final int PREFERENCE_ORDER_VPN = 0; // Netd supports only 0 for VPN.
// Order of per-app OEM preference. See {@link #setOemNetworkPreference}.
@@ -467,6 +461,13 @@
// See {@link ConnectivitySettingsManager#setMobileDataPreferredUids}
@VisibleForTesting
static final int PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED = 30;
+ // Preference order that signifies the network shouldn't be set as a default network for
+ // the UIDs, only give them access to it. TODO : replace this with a boolean
+ // in NativeUidRangeConfig
+ @VisibleForTesting
+ static final int PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT = 999;
+ // Bound for the lowest valid preference order.
+ static final int PREFERENCE_ORDER_LOWEST = 999;
/**
* used internally to clear a wakelock when transitioning
@@ -594,6 +595,13 @@
// Handle private DNS validation status updates.
private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
+ /**
+ * used to remove a network request, either a listener or a real request and call unavailable
+ * arg1 = UID of caller
+ * obj = NetworkRequest
+ */
+ private static final int EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE = 39;
+
/**
* Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
* been tested.
@@ -761,6 +769,7 @@
private Set<String> mWolSupportedInterfaces;
private final TelephonyManager mTelephonyManager;
+ private final CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
private final AppOpsManager mAppOpsManager;
private final LocationPermissionChecker mLocationPermissionChecker;
@@ -1417,6 +1426,12 @@
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
+ if (SdkLevel.isAtLeastT()) {
+ mCarrierPrivilegeAuthenticator =
+ new CarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
+ } else {
+ mCarrierPrivilegeAuthenticator = null;
+ }
// To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -2095,6 +2110,7 @@
newNc.setAdministratorUids(new int[0]);
if (!checkAnyPermissionOf(
callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
+ newNc.setAccessUids(new ArraySet<>());
newNc.setSubscriptionIds(Collections.emptySet());
}
@@ -3344,18 +3360,8 @@
switch (msg.what) {
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
- NetworkCapabilities networkCapabilities = (NetworkCapabilities) arg.second;
- if (networkCapabilities.hasConnectivityManagedCapability()) {
- Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
- }
- if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
- // Make sure the original object is not mutated. NetworkAgent normally
- // makes a copy of the capabilities when sending the message through
- // the Messenger, but if this ever changes, not making a defensive copy
- // here will give attack vectors to clients using this code path.
- networkCapabilities = new NetworkCapabilities(networkCapabilities);
- networkCapabilities.restrictCapabilitiesForTestNetwork(nai.creatorUid);
- }
+ final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
+ (NetworkCapabilities) arg.second);
processCapabilitiesFromAgent(nai, networkCapabilities);
updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
break;
@@ -4151,6 +4157,15 @@
}
}
+ private boolean hasCarrierPrivilegeForNetworkRequest(int callingUid,
+ NetworkRequest networkRequest) {
+ if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
+ return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(callingUid,
+ networkRequest);
+ }
+ return false;
+ }
+
private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) {
final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
// handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests.
@@ -4174,6 +4189,7 @@
private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
ensureRunningOnConnectivityServiceThread();
+ NetworkRequest requestToBeReleased = null;
for (final NetworkRequestInfo nri : nris) {
mNetworkRequestInfoLogs.log("REGISTER " + nri);
checkNrisConsistency(nri);
@@ -4188,7 +4204,15 @@
}
}
}
+ if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ if (!hasCarrierPrivilegeForNetworkRequest(nri.mUid, req)
+ && !checkConnectivityRestrictedNetworksPermission(
+ nri.mPid, nri.mUid)) {
+ requestToBeReleased = req;
+ }
+ }
}
+
// If this NRI has a satisfier already, it is replacing an older request that
// has been removed. Track it.
final NetworkRequest activeRequest = nri.getActiveRequest();
@@ -4198,6 +4222,11 @@
}
}
+ if (requestToBeReleased != null) {
+ releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased);
+ return;
+ }
+
if (mFlags.noRematchAllRequestsOnRegister()) {
rematchNetworksAndRequests(nris);
} else {
@@ -5037,6 +5066,11 @@
/* callOnUnavailable */ false);
break;
}
+ case EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE: {
+ handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
+ /* callOnUnavailable */ true);
+ break;
+ }
case EVENT_SET_ACCEPT_UNVALIDATED: {
Network network = (Network) msg.obj;
handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
@@ -6188,6 +6222,9 @@
if (nc.isPrivateDnsBroken()) {
throw new IllegalArgumentException("Can't request broken private DNS");
}
+ if (nc.hasAccessUids()) {
+ throw new IllegalArgumentException("Can't request access UIDs");
+ }
}
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@@ -6341,12 +6378,24 @@
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
String callingPackageName, String callingAttributionTag) {
if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
- enforceConnectivityRestrictedNetworksPermission();
+ if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ enforceConnectivityRestrictedNetworksPermission();
+ }
} else {
enforceChangePermission(callingPackageName, callingAttributionTag);
}
}
+ private boolean checkConnectivityRestrictedNetworksPermission(int callerPid, int callerUid) {
+ if (checkAnyPermissionOf(callerPid, callerUid,
+ android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
+ || checkAnyPermissionOf(callerPid, callerUid,
+ android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
+ return true;
+ }
+ return false;
+ }
+
@Override
public boolean requestBandwidthUpdate(Network network) {
enforceAccessPermission();
@@ -6410,9 +6459,9 @@
ensureRequestableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
- ensureValidNetworkSpecifier(networkCapabilities);
restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
callingUid, callingPackageName);
+ ensureValid(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -6529,6 +6578,13 @@
EVENT_RELEASE_NETWORK_REQUEST, mDeps.getCallingUid(), 0, networkRequest));
}
+ private void releaseNetworkRequestAndCallOnUnavailable(NetworkRequest networkRequest) {
+ ensureNetworkRequestHasType(networkRequest);
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE, mDeps.getCallingUid(), 0,
+ networkRequest));
+ }
+
private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
if (mNetworkProviderInfos.containsKey(npi.messenger)) {
// Avoid creating duplicates. even if an app makes a direct AIDL call.
@@ -6926,28 +6982,18 @@
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId,
int uid) {
- if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
- // Strictly, sanitizing here is unnecessary as the capabilities will be sanitized in
- // the call to mixInCapabilities below anyway, but sanitizing here means the NAI never
- // sees capabilities that may be malicious, which might prevent mistakes in the future.
- networkCapabilities = new NetworkCapabilities(networkCapabilities);
- networkCapabilities.restrictCapabilitiesForTestNetwork(uid);
- }
- LinkProperties lp = new LinkProperties(linkProperties);
-
- final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
+ // At this point the capabilities/properties are untrusted and unverified, e.g. checks that
+ // the capabilities' access UID comply with security limitations. They will be sanitized
+ // as the NAI registration finishes, in handleRegisterNetworkAgent(). This is
+ // because some of the checks must happen on the handler thread.
final NetworkAgentInfo nai = new NetworkAgentInfo(na,
- new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
+ new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo),
+ linkProperties, networkCapabilities,
currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs,
mQosCallbackTracker, mDeps);
- // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
- processCapabilitiesFromAgent(nai, nc);
- nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
- processLinkPropertiesFromAgent(nai, nai.linkProperties);
-
final String extraInfo = networkInfo.getExtraInfo();
final String name = TextUtils.isEmpty(extraInfo)
? nai.networkCapabilities.getSsid() : extraInfo;
@@ -6962,8 +7008,20 @@
}
private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+ if (VDBG) log("Network Monitor created for " + nai);
+ // nai.nc and nai.lp are the same object that was passed by the network agent if the agent
+ // lives in the same process as this code (e.g. wifi), so make sure this code doesn't
+ // mutate their object
+ final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+ final LinkProperties lp = new LinkProperties(nai.linkProperties);
+ // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
+ processCapabilitiesFromAgent(nai, nc);
+ nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
+ processLinkPropertiesFromAgent(nai, lp);
+ nai.linkProperties = lp;
+
nai.onNetworkMonitorCreated(networkMonitor);
- if (VDBG) log("Got NetworkAgent Messenger");
+
mNetworkAgentInfos.add(nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.put(nai.network.getNetId(), nai);
@@ -6974,6 +7032,7 @@
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
+
nai.notifyRegistered();
NetworkInfo networkInfo = nai.networkInfo;
updateNetworkInfo(nai, networkInfo);
@@ -7421,9 +7480,11 @@
* Stores into |nai| any data coming from the agent that might also be written to the network's
* NetworkCapabilities by ConnectivityService itself. This ensures that the data provided by the
* agent is not lost when updateCapabilities is called.
- * This method should never alter the agent's NetworkCapabilities, only store data in |nai|.
*/
private void processCapabilitiesFromAgent(NetworkAgentInfo nai, NetworkCapabilities nc) {
+ if (nc.hasConnectivityManagedCapability()) {
+ Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
+ }
// Note: resetting the owner UID before storing the agent capabilities in NAI means that if
// the agent attempts to change the owner UID, then nai.declaredCapabilities will not
// actually be the same as the capabilities sent by the agent. Still, it is safer to reset
@@ -7434,6 +7495,7 @@
nc.setOwnerUid(nai.networkCapabilities.getOwnerUid());
}
nai.declaredCapabilities = new NetworkCapabilities(nc);
+ NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid);
}
/** Modifies |newNc| based on the capabilities of |underlyingNetworks| and |agentCaps|. */
@@ -7698,6 +7760,17 @@
return stableRanges;
}
+ private static UidRangeParcel[] intsToUidRangeStableParcels(
+ final @NonNull ArraySet<Integer> uids) {
+ final UidRangeParcel[] stableRanges = new UidRangeParcel[uids.size()];
+ int index = 0;
+ for (int uid : uids) {
+ stableRanges[index] = new UidRangeParcel(uid, uid);
+ index++;
+ }
+ return stableRanges;
+ }
+
private static UidRangeParcel[] toUidRangeStableParcels(UidRange[] ranges) {
final UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.length];
for (int i = 0; i < ranges.length; i++) {
@@ -7768,8 +7841,14 @@
}
}
- private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
- NetworkCapabilities newNc) {
+ private void updateUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
+ @Nullable NetworkCapabilities newNc) {
+ updateVpnUids(nai, prevNc, newNc);
+ updateAccessUids(nai, prevNc, newNc);
+ }
+
+ private void updateVpnUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
+ @Nullable NetworkCapabilities newNc) {
Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUidRanges();
Set<UidRange> newRanges = null == newNc ? null : newNc.getUidRanges();
if (null == prevRanges) prevRanges = new ArraySet<>();
@@ -7828,6 +7907,46 @@
}
}
+ private void updateAccessUids(@NonNull NetworkAgentInfo nai,
+ @Nullable NetworkCapabilities prevNc, @Nullable NetworkCapabilities newNc) {
+ // In almost all cases both NC code for empty access UIDs. return as fast as possible.
+ final boolean prevEmpty = null == prevNc || prevNc.getAccessUidsNoCopy().isEmpty();
+ final boolean newEmpty = null == newNc || newNc.getAccessUidsNoCopy().isEmpty();
+ if (prevEmpty && newEmpty) return;
+
+ final ArraySet<Integer> prevUids =
+ null == prevNc ? new ArraySet<>() : prevNc.getAccessUidsNoCopy();
+ final ArraySet<Integer> newUids =
+ null == newNc ? new ArraySet<>() : newNc.getAccessUidsNoCopy();
+
+ if (prevUids.equals(newUids)) return;
+
+ // This implementation is very simple and vastly faster for sets of Integers than
+ // CompareOrUpdateResult, which is tuned for sets that need to be compared based on
+ // a key computed from the value and has storage for that.
+ final ArraySet<Integer> toRemove = new ArraySet<>(prevUids);
+ final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
+ toRemove.removeAll(newUids);
+ toAdd.removeAll(prevUids);
+
+ try {
+ if (!toAdd.isEmpty()) {
+ mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
+ nai.network.netId,
+ intsToUidRangeStableParcels(toAdd),
+ PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+ }
+ if (!toRemove.isEmpty()) {
+ mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ nai.network.netId,
+ intsToUidRangeStableParcels(toRemove),
+ PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+ }
+ } catch (RemoteException e) {
+ // Netd died. This usually causes a runtime restart anyway.
+ }
+ }
+
public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
ensureRunningOnConnectivityServiceThread();
@@ -9794,7 +9913,7 @@
android.Manifest.permission.NETWORK_STACK);
final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
if (!nc.hasTransport(TRANSPORT_TEST)) {
- throw new SecurityException("Data Stall simluation is only possible for test networks");
+ throw new SecurityException("Data Stall simulation is only possible for test networks");
}
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
new file mode 100644
index 0000000..c5107ad
--- /dev/null
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -0,0 +1,316 @@
+/*
+ * 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.NetworkCapabilities.NET_CAPABILITY_CBS;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.apishim.TelephonyManagerShimImpl;
+import com.android.networkstack.apishim.common.TelephonyManagerShim;
+import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Tracks the uid of the carrier privileged app that provides the carrier config.
+ * Authenticates if the caller has same uid as
+ * carrier privileged app that provides the carrier config
+ * @hide
+ */
+public class CarrierPrivilegeAuthenticator extends BroadcastReceiver {
+ private static final String TAG = CarrierPrivilegeAuthenticator.class.getSimpleName();
+ private static final boolean DBG = true;
+
+ // The context is for the current user (system server)
+ private final Context mContext;
+ private final TelephonyManagerShim mTelephonyManagerShim;
+ private final TelephonyManager mTelephonyManager;
+ @GuardedBy("mLock")
+ private int[] mCarrierServiceUid;
+ @GuardedBy("mLock")
+ private int mModemCount = 0;
+ private final Object mLock = new Object();
+ private final HandlerThread mThread;
+ private final Handler mHandler;
+ @NonNull
+ private final List<CarrierPrivilegesListenerShim> mCarrierPrivilegesChangedListeners =
+ new ArrayList<>();
+
+ public CarrierPrivilegeAuthenticator(@NonNull final Context c,
+ @NonNull final TelephonyManager t,
+ @NonNull final TelephonyManagerShimImpl telephonyManagerShim) {
+ mContext = c;
+ mTelephonyManager = t;
+ mTelephonyManagerShim = telephonyManagerShim;
+ mThread = new HandlerThread(TAG);
+ mThread.start();
+ mHandler = new Handler(mThread.getLooper()) {};
+ synchronized (mLock) {
+ mModemCount = mTelephonyManager.getActiveModemCount();
+ registerForCarrierChanges();
+ updateCarrierServiceUid();
+ }
+ }
+
+ public CarrierPrivilegeAuthenticator(@NonNull final Context c,
+ @NonNull final TelephonyManager t) {
+ mContext = c;
+ mTelephonyManager = t;
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
+ mTelephonyManagerShim = new TelephonyManagerShimImpl(mTelephonyManager);
+ } else {
+ mTelephonyManagerShim = null;
+ }
+ mThread = new HandlerThread(TAG);
+ mThread.start();
+ mHandler = new Handler(mThread.getLooper()) {};
+ synchronized (mLock) {
+ mModemCount = mTelephonyManager.getActiveModemCount();
+ registerForCarrierChanges();
+ updateCarrierServiceUid();
+ }
+ }
+
+ /**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * TODO : migrate to the version in frameworks/libs/net when it's ready
+ *
+ * @hide
+ */
+ public class HandlerExecutor implements Executor {
+ private final Handler mHandler;
+ public HandlerExecutor(@NonNull Handler handler) {
+ mHandler = handler;
+ }
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
+ }
+ }
+
+ /**
+ * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED
+ *
+ * <p>The broadcast receiver is registered with mHandler
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
+ handleActionMultiSimConfigChanged(context, intent);
+ break;
+ default:
+ Log.d(TAG, "Unknown intent received with action: " + intent.getAction());
+ }
+ }
+
+ private void handleActionMultiSimConfigChanged(Context context, Intent intent) {
+ unregisterCarrierPrivilegesListeners();
+ synchronized (mLock) {
+ mModemCount = mTelephonyManager.getActiveModemCount();
+ }
+ registerCarrierPrivilegesListeners();
+ updateCarrierServiceUid();
+ }
+
+ private void registerForCarrierChanges() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+ mContext.registerReceiver(this, filter, null, mHandler);
+ registerCarrierPrivilegesListeners();
+ }
+
+ private void registerCarrierPrivilegesListeners() {
+ final HandlerExecutor executor = new HandlerExecutor(mHandler);
+ int modemCount;
+ synchronized (mLock) {
+ modemCount = mModemCount;
+ }
+ try {
+ for (int i = 0; i < modemCount; i++) {
+ CarrierPrivilegesListenerShim carrierPrivilegesListener =
+ new CarrierPrivilegesListenerShim() {
+ @Override
+ public void onCarrierPrivilegesChanged(
+ @NonNull List<String> privilegedPackageNames,
+ @NonNull int[] privilegedUids) {
+ // Re-trigger the synchronous check (which is also very cheap due
+ // to caching in CarrierPrivilegesTracker). This allows consistency
+ // with the onSubscriptionsChangedListener and broadcasts.
+ updateCarrierServiceUid();
+ }
+ };
+ addCarrierPrivilegesListener(i, executor, carrierPrivilegesListener);
+ mCarrierPrivilegesChangedListeners.add(carrierPrivilegesListener);
+ }
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Encountered exception registering carrier privileges listeners", e);
+ }
+ }
+
+ private void addCarrierPrivilegesListener(int logicalSlotIndex, Executor executor,
+ CarrierPrivilegesListenerShim listener) {
+ if (mTelephonyManagerShim == null) {
+ return;
+ }
+ try {
+ mTelephonyManagerShim.addCarrierPrivilegesListener(
+ logicalSlotIndex, executor, listener);
+ } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ Log.e(TAG, "addCarrierPrivilegesListener API is not available");
+ }
+ }
+
+ private void removeCarrierPrivilegesListener(CarrierPrivilegesListenerShim listener) {
+ if (mTelephonyManagerShim == null) {
+ return;
+ }
+ try {
+ mTelephonyManagerShim.removeCarrierPrivilegesListener(listener);
+ } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ Log.e(TAG, "removeCarrierPrivilegesListener API is not available");
+ }
+ }
+
+ private String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
+ if (mTelephonyManagerShim == null) {
+ return null;
+ }
+ try {
+ return mTelephonyManagerShim.getCarrierServicePackageNameForLogicalSlot(
+ logicalSlotIndex);
+ } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ Log.e(TAG, "getCarrierServicePackageNameForLogicalSlot API is not available");
+ }
+ return null;
+ }
+
+ private void unregisterCarrierPrivilegesListeners() {
+ for (CarrierPrivilegesListenerShim carrierPrivilegesListener :
+ mCarrierPrivilegesChangedListeners) {
+ removeCarrierPrivilegesListener(carrierPrivilegesListener);
+ }
+ mCarrierPrivilegesChangedListeners.clear();
+ }
+
+ /**
+ * Check if network request is allowed based upon carrrier service package.
+ *
+ * Network request for {@link NET_CAPABILITY_CBS} is allowed if the caller has
+ * carrier privilege and provides the carrier config. This function checks if caller
+ * has the same and returns true if it has else false.
+ *
+ * @param callingUid user identifier that uniquely identifies the caller.
+ * @param networkRequest the network request for which the carrier privilege is checked.
+ * @return true if caller has carrier privilege and provides the carrier config else false.
+ */
+ public boolean hasCarrierPrivilegeForNetworkRequest(int callingUid,
+ NetworkRequest networkRequest) {
+ if (callingUid != Process.INVALID_UID) {
+ final int subId = getSubIdFromNetworkSpecifier(
+ networkRequest.getNetworkSpecifier());
+ return callingUid == getCarrierServiceUidForSubId(subId);
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ void updateCarrierServiceUid() {
+ synchronized (mLock) {
+ mCarrierServiceUid = new int[mModemCount];
+ for (int i = 0; i < mModemCount; i++) {
+ mCarrierServiceUid[i] = getCarrierServicePackageUidForSlot(i);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ int getCarrierServiceUidForSubId(int subId) {
+ final int slotId = getSlotIndex(subId);
+ synchronized (mLock) {
+ if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mModemCount) {
+ return mCarrierServiceUid[slotId];
+ }
+ }
+ return Process.INVALID_UID;
+ }
+
+ @VisibleForTesting
+ protected int getSlotIndex(int subId) {
+ return SubscriptionManager.getSlotIndex(subId);
+ }
+
+ @VisibleForTesting
+ int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) {
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+ }
+ return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ }
+
+ @VisibleForTesting
+ int getUidForPackage(String pkgName) {
+ if (pkgName == null) {
+ return Process.INVALID_UID;
+ }
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm != null) {
+ ApplicationInfo applicationInfo = pm.getApplicationInfo(pkgName, 0);
+ if (applicationInfo != null) {
+ return applicationInfo.uid;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException exception) {
+ // Didn't find package. Try other users
+ Log.i(TAG, "Unable to find uid for package " + pkgName);
+ }
+ return Process.INVALID_UID;
+ }
+
+ @VisibleForTesting
+ int getCarrierServicePackageUidForSlot(int slotId) {
+ return getUidForPackage(getCarrierServicePackageNameForLogicalSlot(slotId));
+ }
+}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 1a7248a..b9e2a5f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,7 +17,9 @@
package com.android.server.connectivity;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.transportNamesOf;
import android.annotation.NonNull;
@@ -52,6 +54,7 @@
import android.os.SystemClock;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.telephony.data.NrQosSessionAttributes;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -1188,6 +1191,38 @@
return mConnectivityReport;
}
+ /**
+ * Make sure the NC from network agents don't contain stuff they shouldn't.
+ *
+ * @param nc the capabilities to sanitize
+ * @param creatorUid the UID of the process creating this network agent
+ */
+ public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
+ final int creatorUid) {
+ if (nc.hasTransport(TRANSPORT_TEST)) {
+ nc.restrictCapabilitiesForTestNetwork(creatorUid);
+ }
+ if (!areAccessUidsAcceptableFromNetworkAgent(nc)) {
+ nc.setAccessUids(new ArraySet<>());
+ }
+ }
+
+ private static boolean areAccessUidsAcceptableFromNetworkAgent(
+ @NonNull final NetworkCapabilities nc) {
+ // NCs without access UIDs are fine.
+ if (!nc.hasAccessUids()) return true;
+
+ // On a non-restricted network, access UIDs make no sense
+ if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;
+
+ // If this network has TRANSPORT_TEST, then the caller can do whatever they want to
+ // access UIDs
+ if (nc.hasTransport(TRANSPORT_TEST)) return true;
+
+ // TODO : accept more supported cases
+ return false;
+ }
+
// TODO: Print shorter members first and only print the boolean variable which value is true
// to improve readability.
public String toString() {
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index bea00a9..742044b 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -308,6 +308,48 @@
}
}
+ @Test @IgnoreUpTo(SC_V2)
+ public void testSetAccessUids() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ assertThrows(NullPointerException.class, () -> nc.setAccessUids(null));
+ assertFalse(nc.hasAccessUids());
+ assertFalse(nc.isAccessUid(0));
+ assertFalse(nc.isAccessUid(1000));
+ assertEquals(0, nc.getAccessUids().size());
+ nc.setAccessUids(new ArraySet<>());
+ assertFalse(nc.hasAccessUids());
+ assertFalse(nc.isAccessUid(0));
+ assertFalse(nc.isAccessUid(1000));
+ assertEquals(0, nc.getAccessUids().size());
+
+ final ArraySet<Integer> uids = new ArraySet<>();
+ uids.add(200);
+ uids.add(250);
+ uids.add(-1);
+ uids.add(Integer.MAX_VALUE);
+ nc.setAccessUids(uids);
+ assertNotEquals(nc, new NetworkCapabilities());
+ assertTrue(nc.hasAccessUids());
+
+ final List<Integer> includedList = List.of(-2, 0, 199, 700, 901, 1000, Integer.MIN_VALUE);
+ final List<Integer> excludedList = List.of(-1, 200, 250, Integer.MAX_VALUE);
+ for (final int uid : includedList) {
+ assertFalse(nc.isAccessUid(uid));
+ }
+ for (final int uid : excludedList) {
+ assertTrue(nc.isAccessUid(uid));
+ }
+
+ final Set<Integer> outUids = nc.getAccessUids();
+ assertEquals(4, outUids.size());
+ for (final int uid : includedList) {
+ assertFalse(outUids.contains(uid));
+ }
+ for (final int uid : excludedList) {
+ assertTrue(outUids.contains(uid));
+ }
+ }
+
@Test
public void testParcelNetworkCapabilities() {
final Set<Range<Integer>> uids = new ArraySet<>();
@@ -318,6 +360,10 @@
.addCapability(NET_CAPABILITY_EIMS)
.addCapability(NET_CAPABILITY_NOT_METERED);
if (isAtLeastS()) {
+ final ArraySet<Integer> accessUids = new ArraySet<>();
+ accessUids.add(4);
+ accessUids.add(9);
+ netCap.setAccessUids(accessUids);
netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2));
netCap.setUids(uids);
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index ef5dc77..344482b 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -23,7 +23,6 @@
import android.net.INetworkAgentRegistry
import android.net.InetAddresses
import android.net.IpPrefix
-import android.net.KeepalivePacketData
import android.net.LinkAddress
import android.net.LinkProperties
import android.net.NattKeepalivePacketData
@@ -42,6 +41,7 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkInfo
@@ -53,7 +53,6 @@
import android.net.QosCallback
import android.net.QosCallbackException
import android.net.QosCallback.QosCallbackRegistrationException
-import android.net.QosFilter
import android.net.QosSession
import android.net.QosSessionAttributes
import android.net.QosSocketInfo
@@ -67,7 +66,6 @@
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
-import android.os.Looper
import android.os.Message
import android.os.SystemClock
import android.telephony.TelephonyManager
@@ -82,6 +80,8 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.Losing
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkAgent
@@ -100,7 +100,6 @@
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
import com.android.testutils.TestableNetworkCallback
import org.junit.After
-import org.junit.Assert.assertArrayEquals
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
@@ -462,6 +461,36 @@
}
}
+ private fun ncWithAccessUids(vararg uids: Int) = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .setAccessUids(uids.toSet()).build()
+
+ @Test
+ fun testRejectedUpdates() {
+ val callback = TestableNetworkCallback()
+ // will be cleaned up in tearDown
+ registerNetworkCallback(makeTestNetworkRequest(), callback)
+ val agent = createNetworkAgent(initialNc = ncWithAccessUids(200))
+ agent.register()
+ agent.markConnected()
+
+ // Make sure the UIDs have been ignored.
+ callback.expectCallback<Available>(agent.network!!)
+ callback.expectCapabilitiesThat(agent.network!!) {
+ it.accessUids.isEmpty() && !it.hasCapability(NET_CAPABILITY_VALIDATED)
+ }
+ callback.expectCallback<LinkPropertiesChanged>(agent.network!!)
+ callback.expectCallback<BlockedStatus>(agent.network!!)
+ callback.expectCapabilitiesThat(agent.network!!) {
+ it.accessUids.isEmpty() && it.hasCapability(NET_CAPABILITY_VALIDATED)
+ }
+ callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+
+ // Make sure that the UIDs are also ignored upon update
+ agent.sendNetworkCapabilities(ncWithAccessUids(200, 300))
+ callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+ }
+
@Test
fun testSendScore() {
// This test will create two networks and check that the one with the stronger
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index 4dc86ff..365c0cf 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -21,6 +21,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -109,6 +110,9 @@
case TRANSPORT_WIFI_AWARE:
mScore = new NetworkScore.Builder().setLegacyInt(20).build();
break;
+ case TRANSPORT_TEST:
+ mScore = new NetworkScore.Builder().build();
+ break;
case TRANSPORT_VPN:
mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
// VPNs deduce the SUSPENDED capability from their underlying networks and there
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 652aee9..2985c41 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -23,6 +23,7 @@
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.GET_INTENT_SENDER_INTENT;
import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_FACTORY;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STACK;
@@ -108,6 +109,7 @@
import static android.net.NetworkCapabilities.REDACT_NONE;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -1490,6 +1492,10 @@
r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new);
}
+ private UidRangeParcel[] intToUidRangeStableParcels(final @NonNull Set<Integer> ranges) {
+ return ranges.stream().map(r -> new UidRangeParcel(r, r)).toArray(UidRangeParcel[]::new);
+ }
+
private VpnManagerService makeVpnManagerService() {
final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
public int getCallingUid() {
@@ -3813,14 +3819,14 @@
public void testNoMutableNetworkRequests() throws Exception {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
- NetworkRequest request1 = new NetworkRequest.Builder()
+ final NetworkRequest request1 = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_VALIDATED)
.build();
- NetworkRequest request2 = new NetworkRequest.Builder()
+ final NetworkRequest request2 = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
.build();
- Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback()));
assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent));
assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback()));
@@ -3828,6 +3834,36 @@
}
@Test
+ public void testNoAccessUidsInNetworkRequests() throws Exception {
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
+ final NetworkRequest r = new NetworkRequest.Builder().build();
+ final ArraySet<Integer> accessUids = new ArraySet<>();
+ accessUids.add(6);
+ accessUids.add(9);
+ r.networkCapabilities.setAccessUids(accessUids);
+
+ final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+ final NetworkCallback cb = new NetworkCallback();
+
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ assertThrows(expected, () -> mCm.requestNetwork(r, cb));
+ assertThrows(expected, () -> mCm.requestNetwork(r, pendingIntent));
+ assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb));
+ assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb, handler));
+ assertThrows(expected, () -> mCm.registerNetworkCallback(r, pendingIntent));
+ assertThrows(expected, () -> mCm.registerBestMatchingNetworkCallback(r, cb, handler));
+
+ // Make sure that resetting the access UIDs to the empty set will allow calling
+ // requestNetwork and registerNetworkCallback.
+ r.networkCapabilities.setAccessUids(Collections.emptySet());
+ mCm.requestNetwork(r, cb);
+ mCm.unregisterNetworkCallback(cb);
+ mCm.registerNetworkCallback(r, cb);
+ mCm.unregisterNetworkCallback(cb);
+ }
+
+ @Test
public void testMMSonWiFi() throws Exception {
// Test bringing up cellular without MMS NetworkRequest gets reaped
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -5727,6 +5763,22 @@
}
}
+ /**
+ * Validate the callback flow CBS request without carrier privilege.
+ */
+ @Test
+ public void testCBSRequestWithoutCarrierPrivilege() throws Exception {
+ final NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+ TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_CBS).build();
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+
+ mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
+ // Now file the test request and expect it.
+ mCm.requestNetwork(nr, networkCallback);
+ networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+
private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }
@@ -14555,6 +14607,75 @@
() -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback()));
}
+ @Test
+ public void testAccessUids() throws Exception {
+ final int preferenceOrder =
+ ConnectivityService.PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_TEST)
+ .build(),
+ cb);
+
+ final ArraySet<Integer> uids = new ArraySet<>();
+ uids.add(200);
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setAccessUids(uids)
+ .build();
+ final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(TRANSPORT_TEST,
+ new LinkProperties(), nc);
+ agent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(agent);
+
+ final InOrder inOrder = inOrder(mMockNetd);
+ final NativeUidRangeConfig uids200Parcel = new NativeUidRangeConfig(
+ agent.getNetwork().getNetId(),
+ intToUidRangeStableParcels(uids),
+ preferenceOrder);
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
+
+ uids.add(300);
+ uids.add(400);
+ nc.setAccessUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+
+ uids.remove(200);
+ final NativeUidRangeConfig uids300400Parcel = new NativeUidRangeConfig(
+ agent.getNetwork().getNetId(),
+ intToUidRangeStableParcels(uids),
+ preferenceOrder);
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
+
+ nc.setAccessUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
+
+ uids.clear();
+ uids.add(600);
+ nc.setAccessUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ final NativeUidRangeConfig uids600Parcel = new NativeUidRangeConfig(
+ agent.getNetwork().getNetId(),
+ intToUidRangeStableParcels(uids),
+ preferenceOrder);
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
+
+ uids.clear();
+ nc.setAccessUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().isEmpty());
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
+ }
+
/**
* Validate request counts are counted accurately on setProfileNetworkPreference on set/replace.
*/
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
new file mode 100644
index 0000000..d193aa9
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
+
+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.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+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 android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
+import android.telephony.TelephonyManager;
+
+import com.android.networkstack.apishim.TelephonyManagerShimImpl;
+import com.android.networkstack.apishim.common.TelephonyManagerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for CarrierPrivilegeAuthenticatorTest.
+ *
+ * Build, install and run with:
+ * runtest frameworks-net -c com.android.server.connectivity.CarrierPrivilegeAuthenticatorTest
+ */
+@RunWith(DevSdkIgnoreRunner.class)
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+public class CarrierPrivilegeAuthenticatorTest {
+ private static final String PACKAGE_NAME =
+ CarrierPrivilegeAuthenticatorTest.class.getPackage().getName();
+ private static final int TEST_SIM_SLOT_INDEX = 0;
+ private static final int TEST_SUBSCRIPTION_ID_1 = 2;
+ private static final int TEST_SUBSCRIPTION_ID_2 = 3;
+
+ @NonNull private final Context mContext;
+ @NonNull private final TelephonyManager mTelephonyManager;
+ @NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim;
+ @NonNull private final PackageManager mPackageManager;
+ @NonNull private CarrierPrivilegeAuthenticatorChild mCarrierPrivilegeAuthenticator;
+ private final int mCarrierConfigPkgUid = 12345;
+ private final String mTestPkg = "com.android.server.connectivity.test";
+
+ public class CarrierPrivilegeAuthenticatorChild extends CarrierPrivilegeAuthenticator {
+ CarrierPrivilegeAuthenticatorChild(@NonNull final Context c,
+ @NonNull final TelephonyManager t) {
+ super(c, t, mTelephonyManagerShim);
+ }
+ @Override
+ protected int getSlotIndex(int subId) {
+ return subId;
+ }
+ }
+
+ public CarrierPrivilegeAuthenticatorTest() {
+ mContext = mock(Context.class);
+ mTelephonyManager = mock(TelephonyManager.class);
+ mTelephonyManagerShim = mock(TelephonyManagerShimImpl.class);
+ mPackageManager = mock(PackageManager.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(mTestPkg).when(mTelephonyManagerShim)
+ .getCarrierServicePackageNameForLogicalSlot(anyInt());
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = mCarrierConfigPkgUid;
+ doReturn(applicationInfo).when(mPackageManager)
+ .getApplicationInfo(eq(mTestPkg), anyInt());
+ mCarrierPrivilegeAuthenticator =
+ new CarrierPrivilegeAuthenticatorChild(mContext, mTelephonyManager);
+ }
+
+ private IntentFilter getIntentFilter() {
+ final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mContext).registerReceiver(any(), captor.capture(), any(), any());
+ return captor.getValue();
+ }
+
+ private List<TelephonyManagerShim.CarrierPrivilegesListenerShim>
+ getCarrierPrivilegesListeners() {
+ final ArgumentCaptor<TelephonyManagerShim.CarrierPrivilegesListenerShim> captor =
+ ArgumentCaptor.forClass(TelephonyManagerShim.CarrierPrivilegesListenerShim.class);
+ try {
+ verify(mTelephonyManagerShim, atLeastOnce())
+ .addCarrierPrivilegesListener(anyInt(), any(), captor.capture());
+ } catch (UnsupportedApiLevelException e) {
+
+ }
+ return captor.getAllValues();
+ }
+
+ private Intent buildTestMultiSimConfigBroadcastIntent() {
+ final Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED);
+ return intent;
+ }
+ @Test
+ public void testConstructor() throws Exception {
+ verify(mContext).registerReceiver(
+ eq(mCarrierPrivilegeAuthenticator),
+ any(IntentFilter.class),
+ any(),
+ any());
+ final IntentFilter filter = getIntentFilter();
+ assertEquals(1, filter.countActions());
+ assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED));
+
+ verify(mTelephonyManagerShim, times(2))
+ .addCarrierPrivilegesListener(anyInt(), any(), any());
+ verify(mTelephonyManagerShim)
+ .addCarrierPrivilegesListener(eq(0), any(), any());
+ verify(mTelephonyManagerShim)
+ .addCarrierPrivilegesListener(eq(1), any(), any());
+ assertEquals(2, getCarrierPrivilegesListeners().size());
+
+ final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ new TelephonyNetworkSpecifier(0);
+ final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+ networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+ networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+ mCarrierConfigPkgUid, networkRequestBuilder.build()));
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+ mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+ }
+
+ @Test
+ public void testMultiSimConfigChanged() throws Exception {
+ doReturn(1).when(mTelephonyManager).getActiveModemCount();
+ final List<TelephonyManagerShim.CarrierPrivilegesListenerShim> carrierPrivilegesListeners =
+ getCarrierPrivilegesListeners();
+
+ mCarrierPrivilegeAuthenticator.onReceive(
+ mContext, buildTestMultiSimConfigBroadcastIntent());
+ for (TelephonyManagerShim.CarrierPrivilegesListenerShim carrierPrivilegesListener
+ : carrierPrivilegesListeners) {
+ verify(mTelephonyManagerShim)
+ .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener));
+ }
+
+ // Expect a new CarrierPrivilegesListener to have been registered for slot 0, and none other
+ // (2 previously registered during startup, for slots 0 & 1)
+ verify(mTelephonyManagerShim, times(3))
+ .addCarrierPrivilegesListener(anyInt(), any(), any());
+ verify(mTelephonyManagerShim, times(2))
+ .addCarrierPrivilegesListener(eq(0), any(), any());
+
+ final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ new TelephonyNetworkSpecifier(0);
+ final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+ networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+ networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+ mCarrierConfigPkgUid, networkRequestBuilder.build()));
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+ mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+ }
+
+ @Test
+ public void testOnCarrierPrivilegesChanged() throws Exception {
+ final TelephonyManagerShim.CarrierPrivilegesListenerShim listener =
+ getCarrierPrivilegesListeners().get(0);
+
+ final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ new TelephonyNetworkSpecifier(0);
+ final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+ networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+ networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = mCarrierConfigPkgUid + 1;
+ doReturn(applicationInfo).when(mPackageManager)
+ .getApplicationInfo(eq(mTestPkg), anyInt());
+ listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {});
+
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+ mCarrierConfigPkgUid, networkRequestBuilder.build()));
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+ mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 0e2c293..6872f80 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -28,7 +28,7 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+import static com.android.server.net.NetworkStatsFactory.kernelToTag;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 45f033c..76c0c38 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -198,6 +198,7 @@
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
private @Mock IBpfMap<U32, U8> mUidCounterSetMap;
+ private @Mock NetworkStatsService.TagStatsDeleter mTagStatsDeleter;
private NetworkStatsService mService;
private INetworkStatsSession mSession;
@@ -358,6 +359,11 @@
public IBpfMap<U32, U8> getUidCounterSetMap() {
return mUidCounterSetMap;
}
+
+ @Override
+ public NetworkStatsService.TagStatsDeleter getTagStatsDeleter() {
+ return mTagStatsDeleter;
+ }
};
}
@@ -696,8 +702,10 @@
final Intent intent = new Intent(ACTION_UID_REMOVED);
intent.putExtra(EXTRA_UID, UID_BLUE);
mServiceContext.sendBroadcast(intent);
+ verify(mTagStatsDeleter).deleteTagData(UID_BLUE);
intent.putExtra(EXTRA_UID, UID_RED);
mServiceContext.sendBroadcast(intent);
+ verify(mTagStatsDeleter).deleteTagData(UID_RED);
// existing uid and total should remain unchanged; but removed UID
// should be gone completely.