Merge "Modify the test boundary for read/write test" into main
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 19bcff9..e84573b 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -126,7 +126,7 @@
// Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
cc_library {
name: "libcom_android_networkstack_tethering_util_jni",
- sdk_version: "30",
+ sdk_version: "current",
apex_available: [
"com.android.tethering",
],
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 55a96ac..6c3e89d 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -99,3 +99,11 @@
description: "Flag for oem deny chains blocked reasons API"
bug: "328732146"
}
+
+flag {
+ name: "blocked_reason_network_restricted"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for BLOCKED_REASON_NETWORK_RESTRICTED API"
+ bug: "339559837"
+}
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index d233f3e..cd7307f 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -51,6 +51,7 @@
field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20
+ field @FlaggedApi("com.android.net.flags.blocked_reason_network_restricted") public static final int BLOCKED_REASON_NETWORK_RESTRICTED = 256; // 0x100
field public static final int BLOCKED_REASON_NONE = 0; // 0x0
field @FlaggedApi("com.android.net.flags.blocked_reason_oem_deny_chains") public static final int BLOCKED_REASON_OEM_DENY = 128; // 0x80
field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 48ed732..0b37fa5 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -132,6 +132,8 @@
"com.android.net.flags.metered_network_firewall_chains";
static final String BLOCKED_REASON_OEM_DENY_CHAINS =
"com.android.net.flags.blocked_reason_oem_deny_chains";
+ static final String BLOCKED_REASON_NETWORK_RESTRICTED =
+ "com.android.net.flags.blocked_reason_network_restricted";
}
/**
@@ -928,6 +930,17 @@
public static final int BLOCKED_REASON_OEM_DENY = 1 << 7;
/**
+ * Flag to indicate that an app does not have permission to access the specified network,
+ * for example, because it does not have the {@link android.Manifest.permission#INTERNET}
+ * permission.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.BLOCKED_REASON_NETWORK_RESTRICTED)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_NETWORK_RESTRICTED = 1 << 8;
+
+ /**
* Flag to indicate that an app is subject to Data saver restrictions that would
* result in its metered network access being blocked.
*
@@ -968,6 +981,7 @@
BLOCKED_REASON_LOW_POWER_STANDBY,
BLOCKED_REASON_APP_BACKGROUND,
BLOCKED_REASON_OEM_DENY,
+ BLOCKED_REASON_NETWORK_RESTRICTED,
BLOCKED_METERED_REASON_DATA_SAVER,
BLOCKED_METERED_REASON_USER_RESTRICTED,
BLOCKED_METERED_REASON_ADMIN_DISABLED,
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index 02ef8b7..c726dab 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -118,7 +118,7 @@
* @hide
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = 35) // TODO: change to VANILLA_ICE_CREAM.
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
public static final long NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION = 333340911L;
private ConnectivityCompatChanges() {
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 0e1da93..dc67f83 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -433,6 +433,7 @@
ALOGE("Failed to set bpf.progs_loaded property to 1.");
return 125;
}
+ ALOGI("success.");
return 0;
}
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index 2b5f5c7..289b4d7 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -736,15 +736,15 @@
domain selinux_context = getDomainFromSelinuxContext(md[i].selinux_context);
if (specified(selinux_context)) {
ALOGI("map %s selinux_context [%-32s] -> %d -> '%s' (%s)", mapNames[i].c_str(),
- md[i].selinux_context, selinux_context, lookupSelinuxContext(selinux_context),
- lookupPinSubdir(selinux_context));
+ md[i].selinux_context, static_cast<int>(selinux_context),
+ lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
}
domain pin_subdir = getDomainFromPinSubdir(md[i].pin_subdir);
if (unrecognized(pin_subdir)) return -ENOTDIR;
if (specified(pin_subdir)) {
ALOGI("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir,
- pin_subdir, lookupPinSubdir(pin_subdir));
+ static_cast<int>(pin_subdir), lookupPinSubdir(pin_subdir));
}
// Format of pin location is /sys/fs/bpf/<pin_subdir|prefix>map_<objName>_<mapName>
@@ -974,13 +974,14 @@
if (specified(selinux_context)) {
ALOGI("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(),
- cs[i].prog_def->selinux_context, selinux_context,
+ cs[i].prog_def->selinux_context, static_cast<int>(selinux_context),
lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
}
if (specified(pin_subdir)) {
ALOGI("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(),
- cs[i].prog_def->pin_subdir, pin_subdir, lookupPinSubdir(pin_subdir));
+ cs[i].prog_def->pin_subdir, static_cast<int>(pin_subdir),
+ lookupPinSubdir(pin_subdir));
}
// strip any potential $foo suffix
diff --git a/netd/Android.bp b/netd/Android.bp
index eedbdae..fe4d999 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -72,6 +72,8 @@
"BpfHandlerTest.cpp",
"BpfBaseTest.cpp",
],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
static_libs: [
"libbase",
"libnetd_updatable",
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index b535ebf..91fec90 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "BpfHandler"
+#define LOG_TAG "NetdUpdatable"
#include "BpfHandler.h"
diff --git a/remoteauth/service/jni/Android.bp b/remoteauth/service/jni/Android.bp
index c0ac779..fc91e0c 100644
--- a/remoteauth/service/jni/Android.bp
+++ b/remoteauth/service/jni/Android.bp
@@ -12,7 +12,7 @@
srcs: ["src/lib.rs"],
rustlibs: [
"libbinder_rs",
- "libjni",
+ "libjni_legacy",
"liblazy_static",
"liblog_rust",
"liblogger",
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index c620634..b1925bd 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -76,6 +76,8 @@
"-Wno-unused-parameter",
"-Wthread-safety",
],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
static_libs: [
"libbase",
"libgmock",
diff --git a/service/Android.bp b/service/Android.bp
index 1d74efc..1dd09a9 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -188,7 +188,7 @@
"androidx.annotation_annotation",
"connectivity-net-module-utils-bpf",
"connectivity_native_aidl_interface-lateststable-java",
- "dnsresolver_aidl_interface-V14-java",
+ "dnsresolver_aidl_interface-V15-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-ip",
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 23af0f8..1047232 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -55,6 +55,7 @@
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
@@ -815,8 +816,11 @@
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public int getNetPermForUid(final int uid) {
+ final int appId = UserHandle.getAppId(uid);
try {
- final U8 permissions = sUidPermissionMap.getValue(new S32(uid));
+ // Key of uid permission map is appId
+ // TODO: Rename map name
+ final U8 permissions = sUidPermissionMap.getValue(new S32(appId));
return permissions != null ? permissions.val : PERMISSION_INTERNET;
} catch (ErrnoException e) {
Log.wtf(TAG, "Failed to get permission for uid: " + uid);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 39ea286..ca99935 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -36,8 +36,10 @@
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED;
import static android.net.ConnectivityManager.CALLBACK_IP_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
@@ -120,6 +122,7 @@
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
+import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS;
import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
@@ -485,8 +488,30 @@
private final boolean mBackgroundFirewallChainEnabled;
/**
- * Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in
- * internal handler thread, they don't need a lock.
+ * Uids ConnectivityService tracks blocked status of to send blocked status callbacks.
+ * Key is uid based on mAsUid of registered networkRequestInfo
+ * Value is count of registered networkRequestInfo
+ *
+ * This is necessary because when a firewall chain is enabled or disabled, that affects all UIDs
+ * on the system, not just UIDs on that firewall chain. For example, entering doze mode affects
+ * all UIDs that are not on the dozable chain. ConnectivityService doesn't know which UIDs are
+ * running. But it only needs to send onBlockedStatusChanged to UIDs that have at least one
+ * NetworkCallback registered.
+ *
+ * UIDs are added to this list on the binder thread when processing requestNetwork and similar
+ * IPCs. They are removed from this list on the handler thread, when the callback unregistration
+ * is fully processed. They cannot be unregistered when the unregister IPC is processed because
+ * sometimes requests are unregistered on the handler thread.
+ */
+ @GuardedBy("mBlockedStatusTrackingUids")
+ private final SparseIntArray mBlockedStatusTrackingUids = new SparseIntArray();
+
+ /**
+ * Stale copy of UID blocked reasons. This is used to send onBlockedStatusChanged
+ * callbacks. This is only used on the handler thread, so it does not require a lock.
+ * On U-, the blocked reasons come from NPMS.
+ * On V+, the blocked reasons come from the BPF map contents and only maintains blocked reasons
+ * of uids that register network callbacks.
*/
private final SparseIntArray mUidBlockedReasons = new SparseIntArray();
@@ -554,6 +579,8 @@
* default network for each app.
* Order ints passed to netd must be in the 0~999 range. Larger values code for
* a lower priority, see {@link NativeUidRangeConfig}.
+ * Note that only the highest priority preference is applied if the uid is the target of
+ * multiple preferences.
*
* Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
* The default request uses PREFERENCE_ORDER_DEFAULT.
@@ -788,11 +815,10 @@
private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50;
/**
- * Event to specify that reasons for why an uid is blocked changed.
- * arg1 = uid
- * arg2 = blockedReasons
+ * Event to update blocked reasons for uids.
+ * obj = List of Pair(uid, blockedReasons)
*/
- private static final int EVENT_UID_BLOCKED_REASON_CHANGED = 51;
+ private static final int EVENT_BLOCKED_REASONS_CHANGED = 51;
/**
* Event to register a new network offer
@@ -853,6 +879,18 @@
private static final int EVENT_UID_FROZEN_STATE_CHANGED = 61;
/**
+ * Event to update firewall socket destroy reasons for uids.
+ * obj = List of Pair(uid, socketDestroyReasons)
+ */
+ private static final int EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS = 62;
+
+ /**
+ * Event to clear firewall socket destroy reasons for all uids.
+ * arg1 = socketDestroyReason
+ */
+ private static final int EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS = 63;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -996,14 +1034,19 @@
// Flag to enable the feature of closing frozen app sockets.
private final boolean mDestroyFrozenSockets;
- // Flag to optimize closing frozen app sockets by waiting for the cellular modem to wake up.
- private final boolean mDelayDestroyFrozenSockets;
+ // Flag to optimize closing app sockets by waiting for the cellular modem to wake up.
+ private final boolean mDelayDestroySockets;
// Flag to allow SysUI to receive connectivity reports for wifi picker UI.
private final boolean mAllowSysUiConnectivityReports;
// Uids that ConnectivityService is pending to close sockets of.
- private final Set<Integer> mPendingFrozenUids = new ArraySet<>();
+ // Key is uid and value is reasons of socket destroy
+ private final SparseIntArray mDestroySocketPendingUids = new SparseIntArray();
+
+ private static final int DESTROY_SOCKET_REASON_NONE = 0;
+ private static final int DESTROY_SOCKET_REASON_FROZEN = 1 << 0;
+ private static final int DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND = 1 << 1;
// Flag to drop packets to VPN addresses ingressing via non-VPN interfaces.
private final boolean mIngressToVpnAddressFiltering;
@@ -1821,7 +1864,12 @@
// To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
// reading existing policy from disk.
- mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);
+ // If shouldTrackUidsForBlockedStatusCallbacks() is true (On V+), ConnectivityService
+ // updates blocked reasons when firewall chain and data saver status is updated based on
+ // bpf map contents instead of receiving callbacks from NPMS
+ if (!shouldTrackUidsForBlockedStatusCallbacks()) {
+ mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);
+ }
final PowerManager powerManager = (PowerManager) context.getSystemService(
Context.POWER_SERVICE);
@@ -1952,8 +2000,7 @@
mDestroyFrozenSockets = mDeps.isAtLeastV() || (mDeps.isAtLeastU()
&& mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION));
- mDelayDestroyFrozenSockets = mDeps.isAtLeastU()
- && mDeps.isFeatureEnabled(context, DELAY_DESTROY_FROZEN_SOCKETS_VERSION);
+ mDelayDestroySockets = mDeps.isFeatureNotChickenedOut(context, DELAY_DESTROY_SOCKETS);
mAllowSysUiConnectivityReports = mDeps.isFeatureNotChickenedOut(
mContext, ALLOW_SYSUI_CONNECTIVITY_REPORTS);
if (mDestroyFrozenSockets) {
@@ -3309,14 +3356,38 @@
private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {
@Override
public void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_BLOCKED_REASON_CHANGED,
- uid, blockedReasons));
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ Log.wtf(TAG, "Received unexpected NetworkPolicy callback");
+ return;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_BLOCKED_REASONS_CHANGED,
+ List.of(new Pair<>(uid, blockedReasons))));
}
};
- private void handleUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
- maybeNotifyNetworkBlockedForNewState(uid, blockedReasons);
- setUidBlockedReasons(uid, blockedReasons);
+ private boolean shouldTrackUidsForBlockedStatusCallbacks() {
+ return mDeps.isAtLeastV();
+ }
+
+ @VisibleForTesting
+ void handleBlockedReasonsChanged(List<Pair<Integer, Integer>> reasonsList) {
+ for (Pair<Integer, Integer> reasons: reasonsList) {
+ final int uid = reasons.first;
+ final int blockedReasons = reasons.second;
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ synchronized (mBlockedStatusTrackingUids) {
+ if (mBlockedStatusTrackingUids.get(uid) == 0) {
+ // This uid is not tracked anymore.
+ // This can happen if the network request is unregistered while
+ // EVENT_BLOCKED_REASONS_CHANGED is posted but not processed yet.
+ continue;
+ }
+ }
+ }
+ maybeNotifyNetworkBlockedForNewState(uid, blockedReasons);
+ setUidBlockedReasons(uid, blockedReasons);
+ }
}
static final class UidFrozenStateChangedArgs {
@@ -3347,44 +3418,92 @@
return !mNetworkActivityTracker.isDefaultNetworkActive();
}
- private void handleFrozenUids(int[] uids, int[] frozenStates) {
- final ArraySet<Integer> ownerUids = new ArraySet<>();
+ private boolean shouldTrackFirewallDestroySocketReasons() {
+ return mDeps.isAtLeastV();
+ }
- for (int i = 0; i < uids.length; i++) {
- if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) {
- ownerUids.add(uids[i]);
- } else {
- mPendingFrozenUids.remove(uids[i]);
- }
- }
-
- if (ownerUids.isEmpty()) {
- return;
- }
-
- if (mDelayDestroyFrozenSockets && isCellNetworkIdle()) {
- // Delay closing sockets to avoid waking the cell modem up.
- // Wi-Fi network state is not considered since waking Wi-Fi modem up is much cheaper
- // than waking cell modem up.
- mPendingFrozenUids.addAll(ownerUids);
+ private void updateDestroySocketReasons(final int uid, final int reason,
+ final boolean addReason) {
+ final int destroyReasons = mDestroySocketPendingUids.get(uid, DESTROY_SOCKET_REASON_NONE);
+ if (addReason) {
+ mDestroySocketPendingUids.put(uid, destroyReasons | reason);
} else {
- try {
- mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
- } catch (SocketException | InterruptedIOException | ErrnoException e) {
- loge("Exception in socket destroy: " + e);
+ final int newDestroyReasons = destroyReasons & ~reason;
+ if (newDestroyReasons == DESTROY_SOCKET_REASON_NONE) {
+ mDestroySocketPendingUids.delete(uid);
+ } else {
+ mDestroySocketPendingUids.put(uid, newDestroyReasons);
}
}
}
- private void closePendingFrozenSockets() {
+ private void handleFrozenUids(int[] uids, int[] frozenStates) {
+ ensureRunningOnConnectivityServiceThread();
+ for (int i = 0; i < uids.length; i++) {
+ final int uid = uids[i];
+ final boolean addReason = frozenStates[i] == UID_FROZEN_STATE_FROZEN;
+ updateDestroySocketReasons(uid, DESTROY_SOCKET_REASON_FROZEN, addReason);
+ }
+
+ if (!mDelayDestroySockets || !isCellNetworkIdle()) {
+ destroyPendingSockets();
+ }
+ }
+
+ private void handleUpdateFirewallDestroySocketReasons(
+ List<Pair<Integer, Integer>> reasonsList) {
+ if (!shouldTrackFirewallDestroySocketReasons()) {
+ Log.wtf(TAG, "handleUpdateFirewallDestroySocketReasons is called unexpectedly");
+ return;
+ }
ensureRunningOnConnectivityServiceThread();
- try {
- mDeps.destroyLiveTcpSocketsByOwnerUids(mPendingFrozenUids);
- } catch (SocketException | InterruptedIOException | ErrnoException e) {
- loge("Failed to close pending frozen app sockets: " + e);
+ for (Pair<Integer, Integer> uidSocketDestroyReasons: reasonsList) {
+ final int uid = uidSocketDestroyReasons.first;
+ final int reasons = uidSocketDestroyReasons.second;
+ final boolean destroyByFirewallBackground =
+ (reasons & DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND)
+ != DESTROY_SOCKET_REASON_NONE;
+ updateDestroySocketReasons(uid, DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND,
+ destroyByFirewallBackground);
}
- mPendingFrozenUids.clear();
+
+ if (!mDelayDestroySockets || !isCellNetworkIdle()) {
+ destroyPendingSockets();
+ }
+ }
+
+ private void handleClearFirewallDestroySocketReasons(final int reason) {
+ if (!shouldTrackFirewallDestroySocketReasons()) {
+ Log.wtf(TAG, "handleClearFirewallDestroySocketReasons is called uexpectedly");
+ return;
+ }
+ ensureRunningOnConnectivityServiceThread();
+
+ // Unset reason from all pending uids
+ for (int i = mDestroySocketPendingUids.size() - 1; i >= 0; i--) {
+ final int uid = mDestroySocketPendingUids.keyAt(i);
+ updateDestroySocketReasons(uid, reason, false /* addReason */);
+ }
+ }
+
+ private void destroyPendingSockets() {
+ ensureRunningOnConnectivityServiceThread();
+ if (mDestroySocketPendingUids.size() == 0) {
+ return;
+ }
+
+ Set<Integer> uids = new ArraySet<>();
+ for (int i = 0; i < mDestroySocketPendingUids.size(); i++) {
+ uids.add(mDestroySocketPendingUids.keyAt(i));
+ }
+
+ try {
+ mDeps.destroyLiveTcpSocketsByOwnerUids(uids);
+ } catch (SocketException | InterruptedIOException | ErrnoException e) {
+ loge("Failed to destroy sockets: " + e);
+ }
+ mDestroySocketPendingUids.clear();
}
private void handleReportNetworkActivity(final NetworkActivityParams params) {
@@ -3401,40 +3520,42 @@
isCellNetworkActivity = params.label == TRANSPORT_CELLULAR;
}
- if (mDelayDestroyFrozenSockets
- && params.isActive
- && isCellNetworkActivity
- && !mPendingFrozenUids.isEmpty()) {
- closePendingFrozenSockets();
+ if (mDelayDestroySockets && params.isActive && isCellNetworkActivity) {
+ destroyPendingSockets();
}
}
/**
- * If the cellular network is no longer the default network, close pending frozen sockets.
+ * If the cellular network is no longer the default network, destroy pending sockets.
*
* @param newNetwork new default network
* @param oldNetwork old default network
*/
- private void maybeClosePendingFrozenSockets(NetworkAgentInfo newNetwork,
+ private void maybeDestroyPendingSockets(NetworkAgentInfo newNetwork,
NetworkAgentInfo oldNetwork) {
final boolean isOldNetworkCellular = oldNetwork != null
&& oldNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
final boolean isNewNetworkCellular = newNetwork != null
&& newNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
- if (isOldNetworkCellular
- && !isNewNetworkCellular
- && !mPendingFrozenUids.isEmpty()) {
- closePendingFrozenSockets();
+ if (isOldNetworkCellular && !isNewNetworkCellular) {
+ destroyPendingSockets();
}
}
- private void dumpCloseFrozenAppSockets(IndentingPrintWriter pw) {
- pw.println("CloseFrozenAppSockets:");
+ private void dumpDestroySockets(IndentingPrintWriter pw) {
+ pw.println("DestroySockets:");
pw.increaseIndent();
pw.print("mDestroyFrozenSockets="); pw.println(mDestroyFrozenSockets);
- pw.print("mDelayDestroyFrozenSockets="); pw.println(mDelayDestroyFrozenSockets);
- pw.print("mPendingFrozenUids="); pw.println(mPendingFrozenUids);
+ pw.print("mDelayDestroySockets="); pw.println(mDelayDestroySockets);
+ pw.print("mDestroySocketPendingUids:");
+ pw.increaseIndent();
+ for (int i = 0; i < mDestroySocketPendingUids.size(); i++) {
+ final int uid = mDestroySocketPendingUids.keyAt(i);
+ final int reasons = mDestroySocketPendingUids.valueAt(i);
+ pw.print(uid + ": reasons=" + reasons);
+ }
+ pw.decreaseIndent();
pw.decreaseIndent();
}
@@ -3460,9 +3581,6 @@
@VisibleForTesting
static final String KEY_DESTROY_FROZEN_SOCKETS_VERSION = "destroy_frozen_sockets_version";
- @VisibleForTesting
- static final String DELAY_DESTROY_FROZEN_SOCKETS_VERSION =
- "delay_destroy_frozen_sockets_version";
@VisibleForTesting
public static final String ALLOW_SYSUI_CONNECTIVITY_REPORTS =
@@ -4096,7 +4214,7 @@
dumpAvoidBadWifiSettings(pw);
pw.println();
- dumpCloseFrozenAppSockets(pw);
+ dumpDestroySockets(pw);
pw.println();
dumpBpfProgramStatus(pw);
@@ -5209,7 +5327,7 @@
if (mDefaultRequest == nri) {
mNetworkActivityTracker.updateDefaultNetwork(null /* newNetwork */, nai);
- maybeClosePendingFrozenSockets(null /* newNetwork */, nai);
+ maybeDestroyPendingSockets(null /* newNetwork */, nai);
ensureNetworkTransitionWakelock(nai.toShortString());
}
}
@@ -5486,6 +5604,12 @@
// If there is an active request, then for sure there is a satisfier.
nri.getSatisfier().addRequest(activeRequest);
}
+
+ if (shouldTrackUidsForBlockedStatusCallbacks()
+ && isAppRequest(nri)
+ && !nri.mUidTrackedForBlockedStatus) {
+ Log.wtf(TAG, "Registered nri is not tracked for sending blocked status: " + nri);
+ }
}
if (mFlags.noRematchAllRequestsOnRegister()) {
@@ -5685,6 +5809,11 @@
}
private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
+ handleRemoveNetworkRequest(nri, true /* untrackUids */);
+ }
+
+ private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri,
+ final boolean untrackUids) {
ensureRunningOnConnectivityServiceThread();
for (final NetworkRequest req : nri.mRequests) {
if (null == mNetworkRequests.remove(req)) {
@@ -5719,7 +5848,9 @@
}
}
- nri.mPerUidCounter.decrementCount(nri.mUid);
+ if (untrackUids) {
+ maybeUntrackUidAndClearBlockedReasons(nri);
+ }
mNetworkRequestInfoLogs.log("RELEASE " + nri);
checkNrisConsistency(nri);
@@ -5741,12 +5872,17 @@
}
private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
+ handleRemoveNetworkRequests(nris, true /* untrackUids */);
+ }
+
+ private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris,
+ final boolean untrackUids) {
for (final NetworkRequestInfo nri : nris) {
if (mDefaultRequest == nri) {
// Make sure we never remove the default request.
continue;
}
- handleRemoveNetworkRequest(nri);
+ handleRemoveNetworkRequest(nri, untrackUids);
}
}
@@ -6486,8 +6622,8 @@
handlePrivateDnsValidationUpdate(
(PrivateDnsValidationUpdate) msg.obj);
break;
- case EVENT_UID_BLOCKED_REASON_CHANGED:
- handleUidBlockedReasonChanged(msg.arg1, msg.arg2);
+ case EVENT_BLOCKED_REASONS_CHANGED:
+ handleBlockedReasonsChanged((List) msg.obj);
break;
case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
@@ -6536,6 +6672,12 @@
UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj;
handleFrozenUids(args.mUids, args.mFrozenStates);
break;
+ case EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS:
+ handleUpdateFirewallDestroySocketReasons((List) msg.obj);
+ break;
+ case EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS:
+ handleClearFirewallDestroySocketReasons(msg.arg1);
+ break;
}
}
}
@@ -7352,6 +7494,11 @@
// maximum limit of registered callbacks per UID.
final int mAsUid;
+ // Flag to indicate that uid of this nri is tracked for sending blocked status callbacks.
+ // It is always true on V+ if mMessenger != null. As such, it's not strictly necessary.
+ // it's used only as a safeguard to avoid double counting or leaking.
+ boolean mUidTrackedForBlockedStatus;
+
// Preference order of this request.
final int mPreferenceOrder;
@@ -7401,7 +7548,6 @@
mUid = mDeps.getCallingUid();
mAsUid = asUid;
mPerUidCounter = getRequestCounter(this);
- mPerUidCounter.incrementCountOrThrow(mUid);
/**
* Location sensitive data not included in pending intent. Only included in
* {@link NetworkCallback}.
@@ -7435,7 +7581,6 @@
mAsUid = asUid;
mPendingIntent = null;
mPerUidCounter = getRequestCounter(this);
- mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = callbackFlags;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
@@ -7475,9 +7620,9 @@
mAsUid = nri.mAsUid;
mPendingIntent = nri.mPendingIntent;
mPerUidCounter = nri.mPerUidCounter;
- mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = nri.mCallbackFlags;
mCallingAttributionTag = nri.mCallingAttributionTag;
+ mUidTrackedForBlockedStatus = nri.mUidTrackedForBlockedStatus;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
linkDeathRecipient();
}
@@ -7565,7 +7710,8 @@
+ " " + mRequests
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
+ " callback flags: " + mCallbackFlags
- + " order: " + mPreferenceOrder;
+ + " order: " + mPreferenceOrder
+ + " isUidTracked: " + mUidTrackedForBlockedStatus;
}
}
@@ -7780,13 +7926,6 @@
throw new IllegalArgumentException("Bad timeout specified");
}
- final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
- nextNetworkRequestId(), reqType);
- final NetworkRequestInfo nri = getNriToRegister(
- asUid, networkRequest, messenger, binder, callbackFlags,
- callingAttributionTag);
- if (DBG) log("requestNetwork for " + nri);
-
// For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
// copied from the default request above. (This is necessary to ensure, for example, that
// the callback does not leak sensitive information to unprivileged apps.) Check that the
@@ -7798,7 +7937,13 @@
+ networkCapabilities + " vs. " + defaultNc);
}
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
+ final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
+ nextNetworkRequestId(), reqType);
+ final NetworkRequestInfo nri = getNriToRegister(
+ asUid, networkRequest, messenger, binder, callbackFlags,
+ callingAttributionTag);
+ if (DBG) log("requestNetwork for " + nri);
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_REQUEST, nri);
if (timeoutMs > 0) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
nri), timeoutMs);
@@ -8007,8 +8152,7 @@
NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
callingAttributionTag);
if (DBG) log("pendingRequest for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
- nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT, nri);
return networkRequest;
}
@@ -8047,6 +8191,77 @@
return true;
}
+ private boolean isAppRequest(NetworkRequestInfo nri) {
+ return nri.mMessenger != null || nri.mPendingIntent != null;
+ }
+
+ private void trackUidAndMaybePostCurrentBlockedReason(final NetworkRequestInfo nri) {
+ if (!isAppRequest(nri)) {
+ Log.wtf(TAG, "trackUidAndMaybePostCurrentBlockedReason is called for non app"
+ + "request: " + nri);
+ return;
+ }
+ nri.mPerUidCounter.incrementCountOrThrow(nri.mUid);
+
+ // If nri.mMessenger is null, this nri does not have NetworkCallback so ConnectivityService
+ // does not need to send onBlockedStatusChanged callback for this uid and does not need to
+ // track the uid in mBlockedStatusTrackingUids
+ if (!shouldTrackUidsForBlockedStatusCallbacks() || nri.mMessenger == null) {
+ return;
+ }
+ if (nri.mUidTrackedForBlockedStatus) {
+ Log.wtf(TAG, "Nri is already tracked for sending blocked status: " + nri);
+ return;
+ }
+ nri.mUidTrackedForBlockedStatus = true;
+ synchronized (mBlockedStatusTrackingUids) {
+ final int uid = nri.mAsUid;
+ final int count = mBlockedStatusTrackingUids.get(uid, 0);
+ if (count == 0) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_BLOCKED_REASONS_CHANGED,
+ List.of(new Pair<>(uid, mBpfNetMaps.getUidNetworkingBlockedReasons(uid)))));
+ }
+ mBlockedStatusTrackingUids.put(uid, count + 1);
+ }
+ }
+
+ private void trackUidAndRegisterNetworkRequest(final int event, NetworkRequestInfo nri) {
+ // Post the update of the UID's blocked reasons before posting the message that registers
+ // the callback. This is necessary because if the callback immediately matches a request,
+ // the onBlockedStatusChanged must be called with the correct blocked reasons.
+ // Also, once trackUidAndMaybePostCurrentBlockedReason is called, the register network
+ // request event must be posted, because otherwise the counter for uid will never be
+ // decremented.
+ trackUidAndMaybePostCurrentBlockedReason(nri);
+ mHandler.sendMessage(mHandler.obtainMessage(event, nri));
+ }
+
+ private void maybeUntrackUidAndClearBlockedReasons(final NetworkRequestInfo nri) {
+ if (!isAppRequest(nri)) {
+ // Not an app request.
+ return;
+ }
+ nri.mPerUidCounter.decrementCount(nri.mUid);
+
+ if (!shouldTrackUidsForBlockedStatusCallbacks() || nri.mMessenger == null) {
+ return;
+ }
+ if (!nri.mUidTrackedForBlockedStatus) {
+ Log.wtf(TAG, "Nri is not tracked for sending blocked status: " + nri);
+ return;
+ }
+ nri.mUidTrackedForBlockedStatus = false;
+ synchronized (mBlockedStatusTrackingUids) {
+ final int count = mBlockedStatusTrackingUids.get(nri.mAsUid);
+ if (count > 1) {
+ mBlockedStatusTrackingUids.put(nri.mAsUid, count - 1);
+ } else {
+ mBlockedStatusTrackingUids.delete(nri.mAsUid);
+ mUidBlockedReasons.delete(nri.mAsUid);
+ }
+ }
+ }
+
@Override
public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, IBinder binder,
@@ -8076,7 +8291,7 @@
callingAttributionTag);
if (VDBG) log("listenForNetwork for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_LISTENER, nri);
return networkRequest;
}
@@ -8101,8 +8316,7 @@
callingAttributionTag);
if (VDBG) log("pendingListenForNetwork for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(
- EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT, nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT, nri);
}
/** Returns the next Network provider ID. */
@@ -10085,7 +10299,7 @@
mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
}
mNetworkActivityTracker.updateDefaultNetwork(newDefaultNetwork, oldDefaultNetwork);
- maybeClosePendingFrozenSockets(newDefaultNetwork, oldDefaultNetwork);
+ maybeDestroyPendingSockets(newDefaultNetwork, oldDefaultNetwork);
mProxyTracker.setDefaultProxy(null != newDefaultNetwork
? newDefaultNetwork.linkProperties.getHttpProxy() : null);
resetHttpProxyForNonDefaultNetwork(oldDefaultNetwork);
@@ -11057,7 +11271,7 @@
final boolean metered = nai.networkCapabilities.isMetered();
final boolean vpnBlocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE,
- getBlockedState(blockedReasons, metered, vpnBlocked));
+ getBlockedState(nri.mAsUid, blockedReasons, metered, vpnBlocked));
}
// Notify the requests on this NAI that the network is now lingered.
@@ -11066,7 +11280,21 @@
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
}
- private static int getBlockedState(int reasons, boolean metered, boolean vpnBlocked) {
+ private int getPermissionBlockedState(final int uid, final int reasons) {
+ // Before V, the blocked reasons come from NPMS, and that code already behaves as if the
+ // change was disabled: apps without the internet permission will never be told they are
+ // blocked.
+ if (!mDeps.isAtLeastV()) return reasons;
+
+ if (hasInternetPermission(uid)) return reasons;
+
+ return mDeps.isChangeEnabled(NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, uid)
+ ? reasons | BLOCKED_REASON_NETWORK_RESTRICTED
+ : BLOCKED_REASON_NONE;
+ }
+
+ private int getBlockedState(int uid, int reasons, boolean metered, boolean vpnBlocked) {
+ reasons = getPermissionBlockedState(uid, reasons);
if (!metered) reasons &= ~BLOCKED_METERED_REASON_MASK;
return vpnBlocked
? reasons | BLOCKED_REASON_LOCKDOWN_VPN
@@ -11107,8 +11335,10 @@
? isUidBlockedByVpn(nri.mAsUid, newBlockedUidRanges)
: oldVpnBlocked;
- final int oldBlockedState = getBlockedState(blockedReasons, oldMetered, oldVpnBlocked);
- final int newBlockedState = getBlockedState(blockedReasons, newMetered, newVpnBlocked);
+ final int oldBlockedState = getBlockedState(
+ nri.mAsUid, blockedReasons, oldMetered, oldVpnBlocked);
+ final int newBlockedState = getBlockedState(
+ nri.mAsUid, blockedReasons, newMetered, newVpnBlocked);
if (oldBlockedState != newBlockedState) {
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
newBlockedState);
@@ -11127,8 +11357,9 @@
final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
final int oldBlockedState = getBlockedState(
- mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered, vpnBlocked);
- final int newBlockedState = getBlockedState(blockedReasons, metered, vpnBlocked);
+ uid, mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered, vpnBlocked);
+ final int newBlockedState =
+ getBlockedState(uid, blockedReasons, metered, vpnBlocked);
if (oldBlockedState == newBlockedState) {
continue;
}
@@ -12164,6 +12395,7 @@
// handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the
// callback's binder death.
final NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, requestWithId);
+ nri.mPerUidCounter.incrementCountOrThrow(nri.mUid);
final ConnectivityDiagnosticsCallbackInfo cbInfo =
new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName);
@@ -13258,7 +13490,20 @@
final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate =
getPerAppCallbackRequestsToUpdate();
final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris);
- handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
+ // This method does not need to modify perUidCounter and mBlockedStatusTrackingUids because:
+ // - |nris| only contains per-app network requests created by ConnectivityService which
+ // are internal requests and have no messenger and are not associated with any callbacks,
+ // and so do not need to be tracked in perUidCounter and mBlockedStatusTrackingUids.
+ // - The requests in perAppCallbackRequestsToUpdate are removed, modified, and re-added,
+ // but the same number of requests is removed and re-added, and none of the requests
+ // changes mUid and mAsUid, so the perUidCounter and mBlockedStatusTrackingUids before
+ // and after this method remains the same. Re-adding the requests does not modify
+ // perUidCounter and mBlockedStatusTrackingUids (that is done when the app registers the
+ // request), so removing them must not modify perUidCounter and mBlockedStatusTrackingUids
+ // either.
+ // TODO(b/341228979): Modify nris in place instead of removing them and re-adding them
+ handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate,
+ false /* untrackUids */);
nrisToRegister.addAll(
createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate));
handleRegisterNetworkRequests(nrisToRegister);
@@ -13493,10 +13738,17 @@
throw new IllegalStateException(e);
}
- try {
- mBpfNetMaps.setDataSaverEnabled(enable);
- } catch (ServiceSpecificException | UnsupportedOperationException e) {
- Log.e(TAG, "Failed to set data saver " + enable + " : " + e);
+ synchronized (mBlockedStatusTrackingUids) {
+ try {
+ mBpfNetMaps.setDataSaverEnabled(enable);
+ } catch (ServiceSpecificException | UnsupportedOperationException e) {
+ Log.e(TAG, "Failed to set data saver " + enable + " : " + e);
+ return;
+ }
+
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ updateTrackingUidsBlockedReasons();
+ }
}
}
@@ -13532,10 +13784,20 @@
throw new IllegalArgumentException("setUidFirewallRule with invalid rule: " + rule);
}
- try {
- mBpfNetMaps.setUidRule(chain, uid, firewallRule);
- } catch (ServiceSpecificException e) {
- throw new IllegalStateException(e);
+ synchronized (mBlockedStatusTrackingUids) {
+ try {
+ mBpfNetMaps.setUidRule(chain, uid, firewallRule);
+ } catch (ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ if (shouldTrackUidsForBlockedStatusCallbacks()
+ && mBlockedStatusTrackingUids.get(uid, 0) != 0) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_BLOCKED_REASONS_CHANGED,
+ List.of(new Pair<>(uid, mBpfNetMaps.getUidNetworkingBlockedReasons(uid)))));
+ }
+ if (shouldTrackFirewallDestroySocketReasons()) {
+ maybePostFirewallDestroySocketReasons(chain, Set.of(uid));
+ }
}
}
@@ -13580,23 +13842,40 @@
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ private Set<Integer> getUidsOnFirewallChain(final int chain) throws ErrnoException {
+ if (BpfNetMapsUtils.isFirewallAllowList(chain)) {
+ return mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(chain);
+ } else {
+ return mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(chain);
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private void closeSocketsForFirewallChainLocked(final int chain)
throws ErrnoException, SocketException, InterruptedIOException {
+ final Set<Integer> uidsOnChain = getUidsOnFirewallChain(chain);
if (BpfNetMapsUtils.isFirewallAllowList(chain)) {
// Allowlist means the firewall denies all by default, uids must be explicitly allowed
// So, close all non-system socket owned by uids that are not explicitly allowed
Set<Range<Integer>> ranges = new ArraySet<>();
ranges.add(new Range<>(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE));
- final Set<Integer> exemptUids = mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(chain);
- mDeps.destroyLiveTcpSockets(ranges, exemptUids);
+ mDeps.destroyLiveTcpSockets(ranges, uidsOnChain /* exemptUids */);
} else {
// Denylist means the firewall allows all by default, uids must be explicitly denied
// So, close socket owned by uids that are explicitly denied
- final Set<Integer> ownerUids = mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(chain);
- mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
+ mDeps.destroyLiveTcpSocketsByOwnerUids(uidsOnChain /* ownerUids */);
}
}
+ private void maybePostClearFirewallDestroySocketReasons(int chain) {
+ if (chain != FIREWALL_CHAIN_BACKGROUND) {
+ // TODO (b/300681644): Support other firewall chains
+ return;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS,
+ DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND, 0 /* arg2 */));
+ }
+
@Override
public void setFirewallChainEnabled(final int chain, final boolean enable) {
enforceNetworkStackOrSettingsPermission();
@@ -13613,10 +13892,20 @@
"Chain (" + chain + ") can not be controlled by setFirewallChainEnabled");
}
- try {
- mBpfNetMaps.setChildChain(chain, enable);
- } catch (ServiceSpecificException e) {
- throw new IllegalStateException(e);
+ synchronized (mBlockedStatusTrackingUids) {
+ try {
+ mBpfNetMaps.setChildChain(chain, enable);
+ } catch (ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ updateTrackingUidsBlockedReasons();
+ }
+ if (shouldTrackFirewallDestroySocketReasons() && !enable) {
+ // Clear destroy socket reasons so that CS does not destroy sockets of apps that
+ // have network access.
+ maybePostClearFirewallDestroySocketReasons(chain);
+ }
}
if (mDeps.isAtLeastU() && enable) {
@@ -13628,6 +13917,47 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @GuardedBy("mBlockedStatusTrackingUids")
+ private void updateTrackingUidsBlockedReasons() {
+ if (mBlockedStatusTrackingUids.size() == 0) {
+ return;
+ }
+ final ArrayList<Pair<Integer, Integer>> uidBlockedReasonsList = new ArrayList<>();
+ for (int i = 0; i < mBlockedStatusTrackingUids.size(); i++) {
+ final int uid = mBlockedStatusTrackingUids.keyAt(i);
+ uidBlockedReasonsList.add(
+ new Pair<>(uid, mBpfNetMaps.getUidNetworkingBlockedReasons(uid)));
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_BLOCKED_REASONS_CHANGED,
+ uidBlockedReasonsList));
+ }
+
+ private int getFirewallDestroySocketReasons(final int blockedReasons) {
+ int destroySocketReasons = DESTROY_SOCKET_REASON_NONE;
+ if ((blockedReasons & BLOCKED_REASON_APP_BACKGROUND) != BLOCKED_REASON_NONE) {
+ destroySocketReasons |= DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND;
+ }
+ return destroySocketReasons;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @GuardedBy("mBlockedStatusTrackingUids")
+ private void maybePostFirewallDestroySocketReasons(int chain, Set<Integer> uids) {
+ if (chain != FIREWALL_CHAIN_BACKGROUND) {
+ // TODO (b/300681644): Support other firewall chains
+ return;
+ }
+ final ArrayList<Pair<Integer, Integer>> reasonsList = new ArrayList<>();
+ for (int uid: uids) {
+ final int blockedReasons = mBpfNetMaps.getUidNetworkingBlockedReasons(uid);
+ final int destroySocketReaons = getFirewallDestroySocketReasons(blockedReasons);
+ reasonsList.add(new Pair<>(uid, destroySocketReaons));
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS,
+ reasonsList));
+ }
+
@Override
public boolean getFirewallChainEnabled(final int chain) {
enforceNetworkStackOrSettingsPermission();
@@ -13652,7 +13982,31 @@
return;
}
- mBpfNetMaps.replaceUidChain(chain, uids);
+ synchronized (mBlockedStatusTrackingUids) {
+ // replaceFirewallChain removes uids that are currently on the chain and put |uids| on
+ // the chain.
+ // So this method could change blocked reasons of uids that are currently on chain +
+ // |uids|.
+ final Set<Integer> affectedUids = new ArraySet<>();
+ if (shouldTrackFirewallDestroySocketReasons()) {
+ try {
+ affectedUids.addAll(getUidsOnFirewallChain(chain));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to get uids on chain(" + chain + "): " + e);
+ }
+ for (final int uid: uids) {
+ affectedUids.add(uid);
+ }
+ }
+
+ mBpfNetMaps.replaceUidChain(chain, uids);
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ updateTrackingUidsBlockedReasons();
+ }
+ if (shouldTrackFirewallDestroySocketReasons()) {
+ maybePostFirewallDestroySocketReasons(chain, affectedUids);
+ }
+ }
}
@Override
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 176307d..7ea7f95 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -44,6 +44,8 @@
public static final String BACKGROUND_FIREWALL_CHAIN = "background_firewall_chain";
+ public static final String DELAY_DESTROY_SOCKETS = "delay_destroy_sockets";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java
index 8e6854a..ac02229 100644
--- a/service/src/com/android/server/connectivity/DnsManager.java
+++ b/service/src/com/android/server/connectivity/DnsManager.java
@@ -390,6 +390,7 @@
: new String[0]; // Off
paramsParcel.transportTypes = nc.getTransportTypes();
paramsParcel.meteredNetwork = nc.isMetered();
+ paramsParcel.interfaceNames = lp.getAllInterfaceNames().toArray(new String[0]);
// Prepare to track the validation status of the DNS servers in the
// resolver config when private DNS is in opportunistic or strict mode.
if (useTls) {
@@ -403,13 +404,14 @@
}
Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
- + "%d, %d, %s, %s, %s, %b)", paramsParcel.netId,
+ + "%d, %d, %s, %s, %s, %b, %s)", paramsParcel.netId,
Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains),
paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold,
paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
paramsParcel.retryCount, paramsParcel.tlsName,
Arrays.toString(paramsParcel.tlsServers),
- Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork));
+ Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork,
+ Arrays.toString(paramsParcel.interfaceNames)));
try {
mDnsResolver.setResolverConfiguration(paramsParcel);
diff --git a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
index ac479b8..af4aee5 100644
--- a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
+++ b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
@@ -168,14 +168,18 @@
public void applyMulticastRoutingConfig(
final String iifName, final String oifName, final MulticastRoutingConfig newConfig) {
checkOnHandlerThread();
+ Objects.requireNonNull(iifName, "IifName can't be null");
+ Objects.requireNonNull(oifName, "OifName can't be null");
if (newConfig.getForwardingMode() != FORWARD_NONE) {
// Make sure iif and oif are added as multicast forwarding interfaces
- try {
- maybeAddAndTrackInterface(iifName);
- maybeAddAndTrackInterface(oifName);
- } catch (IllegalStateException e) {
- Log.e(TAG, "Failed to apply multicast routing config, ", e);
+ if (!maybeAddAndTrackInterface(iifName) || !maybeAddAndTrackInterface(oifName)) {
+ Log.e(
+ TAG,
+ "Failed to apply multicast routing config from "
+ + iifName
+ + " to "
+ + oifName);
return;
}
}
@@ -258,9 +262,14 @@
}
}
+ /**
+ * Returns the next available virtual index for multicast routing, or -1 if the number of
+ * virtual interfaces has reached max value.
+ */
private int getNextAvailableVirtualIndex() {
if (mVirtualInterfaces.size() >= MAX_NUM_OF_MULTICAST_VIRTUAL_INTERFACES) {
- throw new IllegalStateException("Can't allocate new multicast virtual interface");
+ Log.e(TAG, "Can't allocate new multicast virtual interface");
+ return -1;
}
for (int i = 0; i < mVirtualInterfaces.size(); i++) {
if (!mVirtualInterfaces.contains(i)) {
@@ -291,12 +300,23 @@
return mVirtualInterfaces.get(virtualIndex);
}
- private void maybeAddAndTrackInterface(String ifName) {
+ /**
+ * Returns {@code true} if the interfaces is added and tracked, or {@code false} when failed
+ * to add the interface.
+ */
+ private boolean maybeAddAndTrackInterface(String ifName) {
checkOnHandlerThread();
- if (getIndexForValue(mVirtualInterfaces, ifName) >= 0) return;
+ if (getIndexForValue(mVirtualInterfaces, ifName) >= 0) return true;
int nextVirtualIndex = getNextAvailableVirtualIndex();
+ if (nextVirtualIndex < 0) {
+ return false;
+ }
int ifIndex = mDependencies.getInterfaceIndex(ifName);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Can't get interface index for " + ifName);
+ return false;
+ }
final StructMif6ctl mif6ctl =
new StructMif6ctl(
nextVirtualIndex,
@@ -309,10 +329,11 @@
Log.d(TAG, "Added mifi " + nextVirtualIndex + " to MIF");
} catch (ErrnoException e) {
Log.e(TAG, "failed to add multicast virtual interface", e);
- return;
+ return false;
}
mVirtualInterfaces.put(nextVirtualIndex, ifName);
mInterfaces.put(ifIndex, ifName);
+ return true;
}
@VisibleForTesting
@@ -798,13 +819,12 @@
NetworkUtils.setsockoptBytes(fd, IPPROTO_IPV6, MRT6_DEL_MFC, bytes);
}
- public Integer getInterfaceIndex(String ifName) {
- try {
- NetworkInterface ni = NetworkInterface.getByName(ifName);
- return ni.getIndex();
- } catch (NullPointerException | SocketException e) {
- return null;
- }
+ /**
+ * Returns the interface index for an interface name, or 0 if the interface index could
+ * not be found.
+ */
+ public int getInterfaceIndex(String ifName) {
+ return Os.if_nametoindex(ifName);
}
public NetworkInterface getNetworkInterface(int physicalIndex) {
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index 92ec0c4..df7010e 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -41,6 +41,10 @@
public static final short IFLA_ADDRESS = 1;
public static final short IFLA_IFNAME = 3;
public static final short IFLA_MTU = 4;
+ public static final short IFLA_INET6_ADDR_GEN_MODE = 8;
+ public static final short IFLA_AF_SPEC = 26;
+
+ public static final short IN6_ADDR_GEN_MODE_NONE = 1;
private int mMtu;
@NonNull
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
index 02d1574..28eeaea 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
@@ -49,7 +49,7 @@
@Field(order = 4, type = Type.U32)
public final long change;
- StructIfinfoMsg(short family, int type, int index, long flags, long change) {
+ public StructIfinfoMsg(short family, int type, int index, long flags, long change) {
this.family = family;
this.type = type;
this.index = index;
diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp
index 7e6b4ec..969ebd4 100644
--- a/staticlibs/native/bpfmapjni/Android.bp
+++ b/staticlibs/native/bpfmapjni/Android.bp
@@ -39,7 +39,7 @@
"-Werror",
"-Wno-unused-parameter",
],
- sdk_version: "30",
+ sdk_version: "current",
min_sdk_version: "30",
apex_available: [
"com.android.tethering",
diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp
index 926590d..e4742ce 100644
--- a/staticlibs/native/tcutils/Android.bp
+++ b/staticlibs/native/tcutils/Android.bp
@@ -21,7 +21,10 @@
name: "libtcutils",
srcs: ["tcutils.cpp"],
export_include_dirs: ["include"],
- header_libs: ["bpf_headers"],
+ header_libs: [
+ "bpf_headers",
+ "libbase_headers",
+ ],
shared_libs: [
"liblog",
],
@@ -31,7 +34,7 @@
"-Werror",
"-Wno-unused-parameter",
],
- sdk_version: "30",
+ sdk_version: "current",
min_sdk_version: "30",
apex_available: [
"com.android.tethering",
diff --git a/staticlibs/native/tcutils/scopeguard.h b/staticlibs/native/tcutils/scopeguard.h
deleted file mode 100644
index 76bbb93..0000000
--- a/staticlibs/native/tcutils/scopeguard.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.
- */
-
-// -----------------------------------------------------------------------------
-// TODO: figure out a way to use libbase_ndk. This is currently not working
-// because of missing apex availability. For now, we can use a copy of
-// ScopeGuard which is very lean compared to unique_fd. This code has been
-// copied verbatim from:
-// https://cs.android.com/android/platform/superproject/+/master:system/libbase/include/android-base/scopeguard.h
-
-#pragma once
-
-#include <utility> // for std::move, std::forward
-
-namespace android {
-namespace base {
-
-// ScopeGuard ensures that the specified functor is executed no matter how the
-// current scope exits.
-template <typename F> class ScopeGuard {
-public:
- ScopeGuard(F &&f) : f_(std::forward<F>(f)), active_(true) {}
-
- ScopeGuard(ScopeGuard &&that) noexcept
- : f_(std::move(that.f_)), active_(that.active_) {
- that.active_ = false;
- }
-
- template <typename Functor>
- ScopeGuard(ScopeGuard<Functor> &&that)
- : f_(std::move(that.f_)), active_(that.active_) {
- that.active_ = false;
- }
-
- ~ScopeGuard() {
- if (active_)
- f_();
- }
-
- ScopeGuard() = delete;
- ScopeGuard(const ScopeGuard &) = delete;
- void operator=(const ScopeGuard &) = delete;
- void operator=(ScopeGuard &&that) = delete;
-
- void Disable() { active_ = false; }
-
- bool active() const { return active_; }
-
-private:
- template <typename Functor> friend class ScopeGuard;
-
- F f_;
- bool active_;
-};
-
-template <typename F> ScopeGuard<F> make_scope_guard(F &&f) {
- return ScopeGuard<F>(std::forward<F>(f));
-}
-
-} // namespace base
-} // namespace android
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
index c82390f..21e781c 100644
--- a/staticlibs/native/tcutils/tcutils.cpp
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -20,8 +20,10 @@
#include "logging.h"
#include "bpf/KernelUtils.h"
-#include "scopeguard.h"
+#include <BpfSyscallWrappers.h>
+#include <android-base/scopeguard.h>
+#include <android-base/unique_fd.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
@@ -39,10 +41,6 @@
#include <unistd.h>
#include <utility>
-#define BPF_FD_JUST_USE_INT
-#include <BpfSyscallWrappers.h>
-#undef BPF_FD_JUST_USE_INT
-
// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
#define CLS_BPF_NAME_LEN 256
@@ -52,6 +50,9 @@
namespace android {
namespace {
+using base::make_scope_guard;
+using base::unique_fd;
+
/**
* IngressPoliceFilterBuilder builds a nlmsg request equivalent to the following
* tc command:
@@ -130,7 +131,7 @@
// class members
const unsigned mBurstInBytes;
const char *mBpfProgPath;
- int mBpfFd;
+ unique_fd mBpfFd;
Request mRequest;
static double getTickInUsec() {
@@ -139,7 +140,7 @@
ALOGE("fopen(\"/proc/net/psched\"): %s", strerror(errno));
return 0.0;
}
- auto scopeGuard = base::make_scope_guard([fp] { fclose(fp); });
+ auto scopeGuard = make_scope_guard([fp] { fclose(fp); });
uint32_t t2us;
uint32_t us2t;
@@ -166,7 +167,6 @@
unsigned burstInBytes, const char* bpfProgPath)
: mBurstInBytes(burstInBytes),
mBpfProgPath(bpfProgPath),
- mBpfFd(-1),
mRequest{
.n = {
.nlmsg_len = sizeof(mRequest),
@@ -298,13 +298,6 @@
}
// clang-format on
- ~IngressPoliceFilterBuilder() {
- // TODO: use unique_fd
- if (mBpfFd != -1) {
- close(mBpfFd);
- }
- }
-
constexpr unsigned getRequestSize() const { return sizeof(Request); }
private:
@@ -332,14 +325,14 @@
}
int initBpfFd() {
- mBpfFd = bpf::retrieveProgram(mBpfProgPath);
- if (mBpfFd == -1) {
+ mBpfFd.reset(bpf::retrieveProgram(mBpfProgPath));
+ if (!mBpfFd.ok()) {
int error = errno;
ALOGE("retrieveProgram failed: %d", error);
return -error;
}
- mRequest.opt.acts.act2.opt.fd.u32 = static_cast<uint32_t>(mBpfFd);
+ mRequest.opt.acts.act2.opt.fd.u32 = static_cast<uint32_t>(mBpfFd.get());
snprintf(mRequest.opt.acts.act2.opt.name.str,
sizeof(mRequest.opt.acts.act2.opt.name.str), "%s:[*fsobj]",
basename(mBpfProgPath));
@@ -370,14 +363,13 @@
int sendAndProcessNetlinkResponse(const void *req, int len) {
// TODO: use unique_fd instead of ScopeGuard
- int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
- if (fd == -1) {
+ unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
+ if (!fd.ok()) {
int error = errno;
ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %d",
error);
return -error;
}
- auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
static constexpr int on = 1;
if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
@@ -460,10 +452,9 @@
}
int hardwareAddressType(const char *interface) {
- int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
- if (fd < 0)
+ unique_fd fd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+ if (!fd.ok())
return -errno;
- auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
struct ifreq ifr = {};
// We use strncpy() instead of strlcpy() since kernel has to be able
@@ -576,12 +567,11 @@
// /sys/fs/bpf/... direct-action
int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto,
const char *bpfProgPath) {
- const int bpfFd = bpf::retrieveProgram(bpfProgPath);
- if (bpfFd == -1) {
+ unique_fd bpfFd(bpf::retrieveProgram(bpfProgPath));
+ if (!bpfFd.ok()) {
ALOGE("retrieveProgram failed: %d", errno);
return -errno;
}
- auto scopeGuard = base::make_scope_guard([bpfFd] { close(bpfFd); });
struct {
nlmsghdr n;
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 59ef20d..44abba2 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -22,7 +22,7 @@
sdk_version: "system_current",
min_sdk_version: "30",
static_libs: [
- "netd_aidl_interface-V14-java",
+ "netd_aidl_interface-V15-java",
],
apex_available: [
"//apex_available:platform", // used from services.net
@@ -45,7 +45,7 @@
cc_library_static {
name: "netd_aidl_interface-lateststable-ndk",
whole_static_libs: [
- "netd_aidl_interface-V14-ndk",
+ "netd_aidl_interface-V15-ndk",
],
apex_available: [
"com.android.resolv",
@@ -56,12 +56,12 @@
cc_defaults {
name: "netd_aidl_interface_lateststable_cpp_static",
- static_libs: ["netd_aidl_interface-V14-cpp"],
+ static_libs: ["netd_aidl_interface-V15-cpp"],
}
cc_defaults {
name: "netd_aidl_interface_lateststable_cpp_shared",
- shared_libs: ["netd_aidl_interface-V14-cpp"],
+ shared_libs: ["netd_aidl_interface-V15-cpp"],
}
aidl_interface {
@@ -167,6 +167,10 @@
version: "14",
imports: [],
},
+ {
+ version: "15",
+ imports: [],
+ },
],
frozen: true,
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/15/.hash
new file mode 100644
index 0000000..afdadcc
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/.hash
@@ -0,0 +1 @@
+2be6ff6fb01645cdddb3bb60f6de5727e5733267
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetd.aidl
new file mode 100644
index 0000000..80b3b62
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetd.aidl
@@ -0,0 +1,260 @@
+/**
+ * Copyright (c) 2016, 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+ boolean isAlive();
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+ boolean bandwidthEnableDataSaver(boolean enable);
+ /**
+ * @deprecated use networkCreate() instead.
+ */
+ void networkCreatePhysical(int netId, int permission);
+ /**
+ * @deprecated use networkCreate() instead.
+ */
+ void networkCreateVpn(int netId, boolean secure);
+ void networkDestroy(int netId);
+ void networkAddInterface(int netId, in @utf8InCpp String iface);
+ void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+ void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+ void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+ void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+ void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+ boolean tetherApplyDnsInterfaces();
+ android.net.TetherStatsParcel[] tetherGetStats();
+ void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+ void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+ @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+ void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+ void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+ int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+ void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+ void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+ void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+ void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+ void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+ void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+ void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+ void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+ void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+ void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+ void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+ void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+ void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+ void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+ void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+ void strictUidCleartextPenalty(int uid, int policyPenalty);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+ */
+ @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+ */
+ void clatdStop(in @utf8InCpp String ifName);
+ boolean ipfwdEnabled();
+ @utf8InCpp String[] ipfwdGetRequesterList();
+ void ipfwdEnableForwarding(in @utf8InCpp String requester);
+ void ipfwdDisableForwarding(in @utf8InCpp String requester);
+ void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+ void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+ void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+ void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+ void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+ void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+ void bandwidthSetGlobalAlert(long bytes);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthAddNaughtyApp(int uid);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthRemoveNaughtyApp(int uid);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthAddNiceApp(int uid);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthRemoveNiceApp(int uid);
+ void tetherStart(in @utf8InCpp String[] dhcpRanges);
+ void tetherStop();
+ boolean tetherIsEnabled();
+ void tetherInterfaceAdd(in @utf8InCpp String ifName);
+ void tetherInterfaceRemove(in @utf8InCpp String ifName);
+ @utf8InCpp String[] tetherInterfaceList();
+ void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+ @utf8InCpp String[] tetherDnsList();
+ void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+ void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+ void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+ void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+ int networkGetDefault();
+ void networkSetDefault(int netId);
+ void networkClearDefault();
+ void networkSetPermissionForNetwork(int netId, int permission);
+ void networkSetPermissionForUser(int permission, in int[] uids);
+ void networkClearPermissionForUser(in int[] uids);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void trafficSetNetPermForUids(int permission, in int[] uids);
+ void networkSetProtectAllow(int uid);
+ void networkSetProtectDeny(int uid);
+ boolean networkCanProtect(int uid);
+ void firewallSetFirewallType(int firewalltype);
+ void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallSetUidRule(int childChain, int uid, int firewallRule);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallEnableChildChain(int childChain, boolean enable);
+ @utf8InCpp String[] interfaceGetList();
+ android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+ void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+ void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+ void interfaceClearAddrs(in @utf8InCpp String ifName);
+ void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+ void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+ void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+ void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+ void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+ void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallRemoveUidInterfaceRules(in int[] uids);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void trafficSwapActiveStatsMap();
+ IBinder getOemNetd();
+ void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+ android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+ void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+ void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+ void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ android.net.TetherStatsParcel[] tetherOffloadGetStats();
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+ void networkCreate(in android.net.NativeNetworkConfig config);
+ void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+ void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+ void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
+ void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks);
+ void networkAllowBypassVpnOnNetwork(boolean allow, int uid, int netId);
+ const int IPV4 = 4;
+ const int IPV6 = 6;
+ const int CONF = 1;
+ const int NEIGH = 2;
+ const String IPSEC_INTERFACE_PREFIX = "ipsec";
+ const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+ const int IPV6_ADDR_GEN_MODE_NONE = 1;
+ const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+ const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+ const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+ const int PENALTY_POLICY_ACCEPT = 1;
+ const int PENALTY_POLICY_LOG = 2;
+ const int PENALTY_POLICY_REJECT = 3;
+ const int CLAT_MARK = 0xdeadc1a7;
+ const int LOCAL_NET_ID = 99;
+ const int DUMMY_NET_ID = 51;
+ const int UNREACHABLE_NET_ID = 52;
+ const String NEXTHOP_NONE = "";
+ const String NEXTHOP_UNREACHABLE = "unreachable";
+ const String NEXTHOP_THROW = "throw";
+ const int PERMISSION_NONE = 0;
+ const int PERMISSION_NETWORK = 1;
+ const int PERMISSION_SYSTEM = 2;
+ const int NO_PERMISSIONS = 0;
+ const int PERMISSION_INTERNET = 4;
+ const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+ const int PERMISSION_UNINSTALLED = (-1) /* -1 */;
+ /**
+ * @deprecated use FIREWALL_ALLOWLIST.
+ */
+ const int FIREWALL_WHITELIST = 0;
+ const int FIREWALL_ALLOWLIST = 0;
+ /**
+ * @deprecated use FIREWALL_DENYLIST.
+ */
+ const int FIREWALL_BLACKLIST = 1;
+ const int FIREWALL_DENYLIST = 1;
+ const int FIREWALL_RULE_ALLOW = 1;
+ const int FIREWALL_RULE_DENY = 2;
+ const int FIREWALL_CHAIN_NONE = 0;
+ const int FIREWALL_CHAIN_DOZABLE = 1;
+ const int FIREWALL_CHAIN_STANDBY = 2;
+ const int FIREWALL_CHAIN_POWERSAVE = 3;
+ const int FIREWALL_CHAIN_RESTRICTED = 4;
+ const String IF_STATE_UP = "up";
+ const String IF_STATE_DOWN = "down";
+ const String IF_FLAG_BROADCAST = "broadcast";
+ const String IF_FLAG_LOOPBACK = "loopback";
+ const String IF_FLAG_POINTOPOINT = "point-to-point";
+ const String IF_FLAG_RUNNING = "running";
+ const String IF_FLAG_MULTICAST = "multicast";
+ const int IPSEC_DIRECTION_IN = 0;
+ const int IPSEC_DIRECTION_OUT = 1;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+ oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+ oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+ oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+ oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+ oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+ oneway void onInterfaceAdded(@utf8InCpp String ifName);
+ oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+ oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+ oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+ oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+ oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+ @utf8InCpp String ifName;
+ @utf8InCpp String hwAddr;
+ @utf8InCpp String ipv4Addr;
+ int prefixLength;
+ @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/IpSecMigrateInfoParcel.aidl
new file mode 100644
index 0000000..975a261
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/IpSecMigrateInfoParcel.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaOnlyImmutable
+parcelable IpSecMigrateInfoParcel {
+ int requestId;
+ int selAddrFamily;
+ int direction;
+ @utf8InCpp String oldSourceAddress;
+ @utf8InCpp String oldDestinationAddress;
+ @utf8InCpp String newSourceAddress;
+ @utf8InCpp String newDestinationAddress;
+ int interfaceId;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+ int mark;
+ int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+ int netId;
+ android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+ int permission;
+ boolean secure;
+ android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+ boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..e77a143
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+ PHYSICAL = 0,
+ VIRTUAL = 1,
+ PHYSICAL_LOCAL = 2,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+ SERVICE = 1,
+ PLATFORM = 2,
+ LEGACY = 3,
+ OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+ @utf8InCpp String destination;
+ @utf8InCpp String ifName;
+ @utf8InCpp String nextHop;
+ int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+ boolean usingLegacyDnsProxy;
+ @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+ int inputInterfaceIndex;
+ int outputInterfaceIndex;
+ byte[] destination;
+ int prefixLength;
+ byte[] srcL2Address;
+ byte[] dstL2Address;
+ int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+ @utf8InCpp String iface;
+ long rxBytes;
+ long rxPackets;
+ long txBytes;
+ long txPackets;
+ int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+ int start;
+ int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+ int netId;
+ android.net.UidRangeParcel[] uidRanges;
+ int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
index 8ccefb2..80b3b62 100644
--- a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
@@ -203,6 +203,7 @@
void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks);
+ void networkAllowBypassVpnOnNetwork(boolean allow, int uid, int netId);
const int IPV4 = 4;
const int IPV6 = 6;
const int CONF = 1;
diff --git a/staticlibs/netd/binder/android/net/INetd.aidl b/staticlibs/netd/binder/android/net/INetd.aidl
index ee27e84..e4c63b9 100644
--- a/staticlibs/netd/binder/android/net/INetd.aidl
+++ b/staticlibs/netd/binder/android/net/INetd.aidl
@@ -1446,4 +1446,27 @@
* - subPriority: unused
*/
void setNetworkAllowlist(in NativeUidRangeConfig[] allowedNetworks);
+
+ /**
+ * Allow the UID to explicitly select the given network even if it is subject to a VPN.
+ *
+ * Throws ServiceSpecificException with error code EEXISTS when trying to add a bypass rule that
+ * already exists, and ENOENT when trying to remove a bypass rule that does not exist.
+ *
+ * netId specific bypass rules can be combined and are allowed to overlap with global VPN
+ * exclusions (by calling networkSetProtectAllow / networkSetProtectDeny, or by setting netId to
+ * 0). Adding or removing global VPN bypass rules does not affect the netId specific rules and
+ * vice versa.
+ *
+ * Note that if netId is set to 0 (NETID_UNSET) this API is equivalent to
+ * networkSetProtectAllow} / #networkSetProtectDeny.
+ *
+ * @param allow whether to allow or disallow the operation.
+ * @param uid the UID
+ * @param netId the netId that the UID is allowed to select.
+ *
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ void networkAllowBypassVpnOnNetwork(boolean allow, int uid, int netId);
}
diff --git a/staticlibs/netd/libnetdutils/InternetAddresses.cpp b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
index 322f1b1..6d98608 100644
--- a/staticlibs/netd/libnetdutils/InternetAddresses.cpp
+++ b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
@@ -16,6 +16,7 @@
#include "netdutils/InternetAddresses.h"
+#include <stdlib.h>
#include <string>
#include <android-base/stringprintf.h>
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
index 4185b05..d5e91c2 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
@@ -24,6 +24,7 @@
* A JUnit Rule that sets feature flags based on `@FeatureFlag` annotations.
*
* This rule enables dynamic control of feature flag states during testing.
+ * And restores the original values after performing tests.
*
* **Usage:**
* ```kotlin
@@ -31,6 +32,8 @@
* @get:Rule
* val setFeatureFlagsRule = SetFeatureFlagsRule(setFlagsMethod = (name, enabled) -> {
* // Custom handling code.
+ * }, (name) -> {
+ * // Custom getter code to retrieve the original values.
* })
*
* // ... test methods with @FeatureFlag annotations
@@ -41,7 +44,10 @@
* }
* ```
*/
-class SetFeatureFlagsRule(val setFlagsMethod: (name: String, enabled: Boolean) -> Unit) : TestRule {
+class SetFeatureFlagsRule(
+ val setFlagsMethod: (name: String, enabled: Boolean?) -> Unit,
+ val getFlagsMethod: (name: String) -> Boolean?
+) : TestRule {
/**
* This annotation marks a test method as requiring a specific feature flag to be configured.
*
@@ -69,13 +75,20 @@
FeatureFlag::class.java
)
+ val valuesToBeRestored = mutableMapOf<String, Boolean?>()
for (featureFlagAnnotation in featureFlagAnnotations) {
+ valuesToBeRestored[featureFlagAnnotation.name] =
+ getFlagsMethod(featureFlagAnnotation.name)
setFlagsMethod(featureFlagAnnotation.name, featureFlagAnnotation.enabled)
}
// Execute the test method, which includes methods annotated with
// @Before, @Test and @After.
base.evaluate()
+
+ valuesToBeRestored.forEach {
+ setFlagsMethod(it.key, it.value)
+ }
}
}
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index f76916a..66362d4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -360,17 +360,29 @@
timeoutMs: Long = defaultTimeoutMs,
errorMsg: String? = null,
test: (T) -> Boolean = { true }
- ) = (poll(timeoutMs) ?: fail("Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
+ ) = (poll(timeoutMs) ?: failWithErrorReason(errorMsg,
+ "Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
.also {
- if (it !is T) fail("Expected callback ${T::class.simpleName}, got $it")
+ if (it !is T) {
+ failWithErrorReason(
+ errorMsg,
+ "Expected callback ${T::class.simpleName}, got $it"
+ )
+ }
if (ANY_NETWORK !== network && it.network != network) {
- fail("Expected network $network for callback : $it")
+ failWithErrorReason(errorMsg, "Expected network $network for callback : $it")
}
if (!test(it)) {
- fail("${errorMsg ?: "Callback doesn't match predicate"} : $it")
+ failWithErrorReason(errorMsg, "Callback doesn't match predicate : $it")
}
} as T
+ // "Nothing" is the return type to declare a function never returns a value.
+ fun failWithErrorReason(errorMsg: String?, errorReason: String): Nothing {
+ val message = if (errorMsg != null) "$errorMsg : $errorReason" else errorReason
+ fail(message)
+ }
+
inline fun <reified T : CallbackEntry> expect(
network: HasNetwork,
timeoutMs: Long = defaultTimeoutMs,
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 6e9d614..e95a81a 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -201,3 +201,8 @@
name: "connectivity-mainline-presubmit-java-defaults",
test_mainline_modules: mainline_presubmit_modules,
}
+
+filegroup {
+ name: "connectivity_mainline_test_map",
+ srcs: ["connectivity_mainline_test.map"],
+}
diff --git a/tests/common/connectivity_mainline_test.map b/tests/common/connectivity_mainline_test.map
new file mode 100644
index 0000000..043312e
--- /dev/null
+++ b/tests/common/connectivity_mainline_test.map
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2024 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.
+#
+
+# Some connectivity tests run on older OS versions, and for those tests, many
+# library dependencies (such as libbase and libc++) need to be linked
+# statically. The tests also need to be linked with a version script to ensure
+# that the statically-linked library isn't exported from the executable, where
+# it would override the shared libraries that the OS itself uses. See
+# b/333438055 for an example of what goes wrong when libc++ is partially
+# exported from an executable.
+{
+ local:
+ *;
+};
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 5ed4696..bf40462 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -36,11 +36,22 @@
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_OEM_DENY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.ConnectivityManager.EXTRA_NETWORK;
import static android.net.ConnectivityManager.EXTRA_NETWORK_REQUEST;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
@@ -196,6 +207,7 @@
import com.android.testutils.CompatUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DeviceConfigRule;
import com.android.testutils.DeviceInfoUtils;
@@ -2472,8 +2484,11 @@
}
}
+ // On V+, ConnectivityService generates blockedReasons based on bpf map contents even if the
+ // otherUid does not exist on device. So if allowlist chain (e.g. background chain) is enabled,
+ // blockedReasons for otherUid will not be BLOCKED_REASON_NONE.
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
- @Test
+ @Test @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testBlockedStatusCallback() throws Exception {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
// shims, and @IgnoreUpTo does not check that.
@@ -3713,6 +3728,260 @@
Process.myUid() + 1, EXPECT_OPEN);
}
+ private int getBlockedReason(final int chain) {
+ switch(chain) {
+ case FIREWALL_CHAIN_DOZABLE:
+ return BLOCKED_REASON_DOZE;
+ case FIREWALL_CHAIN_POWERSAVE:
+ return BLOCKED_REASON_BATTERY_SAVER;
+ case FIREWALL_CHAIN_RESTRICTED:
+ return BLOCKED_REASON_RESTRICTED_MODE;
+ case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+ return BLOCKED_REASON_LOW_POWER_STANDBY;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return BLOCKED_REASON_APP_BACKGROUND;
+ case FIREWALL_CHAIN_STANDBY:
+ return BLOCKED_REASON_APP_STANDBY;
+ case FIREWALL_CHAIN_METERED_DENY_USER:
+ return BLOCKED_METERED_REASON_USER_RESTRICTED;
+ case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+ return BLOCKED_METERED_REASON_ADMIN_DISABLED;
+ case FIREWALL_CHAIN_OEM_DENY_1:
+ case FIREWALL_CHAIN_OEM_DENY_2:
+ case FIREWALL_CHAIN_OEM_DENY_3:
+ return BLOCKED_REASON_OEM_DENY;
+ default:
+ throw new IllegalArgumentException(
+ "Failed to find blockedReasons for chain: " + chain);
+ }
+ }
+
+ private void doTestBlockedReasons_setUidFirewallRule(final int chain, final boolean metered)
+ throws Exception {
+ assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+ // Store current Wi-Fi metered value and update metered value
+ final Network currentWifiNetwork = mCtsNetUtils.ensureWifiConnected();
+ final NetworkCapabilities wifiNetworkCapabilities = callWithShellPermissionIdentity(
+ () -> mCm.getNetworkCapabilities(currentWifiNetwork));
+ final String ssid = unquoteSSID(wifiNetworkCapabilities.getSsid());
+ final boolean oldMeteredValue = wifiNetworkCapabilities.isMetered();
+ final Network wifiNetwork =
+ setWifiMeteredStatusAndWait(ssid, metered, true /* waitForValidation */);
+
+ // Store current firewall chains status. This test operates on the chain that is passed in,
+ // but also always operates on FIREWALL_CHAIN_METERED_DENY_USER to ensure that metered
+ // chains are tested as well.
+ final int myUid = Process.myUid();
+ final boolean wasChainEnabled = runWithShellPermissionIdentity(
+ () -> mCm.getFirewallChainEnabled(chain), NETWORK_SETTINGS);
+ final int previousFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(chain, myUid));
+ final int previousMeteredDenyFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, myUid));
+
+ final DetailedBlockedStatusCallback cb = new DetailedBlockedStatusCallback();
+ networkCallbackRule.requestNetwork(makeWifiNetworkRequest(), cb);
+ testAndCleanup(() -> {
+ int blockedReasonsWithoutChain = BLOCKED_REASON_NONE;
+ int blockedReasonsWithChain = getBlockedReason(chain);
+ int blockedReasonsWithChainAndLockDown =
+ getBlockedReason(chain) | BLOCKED_REASON_LOCKDOWN_VPN;
+ if (metered) {
+ blockedReasonsWithoutChain |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ blockedReasonsWithChain |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ blockedReasonsWithChainAndLockDown |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ }
+
+ // Set RULE_DENY on target chain and metered deny chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ mCm.setUidFirewallRule(chain, myUid, FIREWALL_RULE_DENY);
+ mCm.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, myUid,
+ FIREWALL_RULE_DENY);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, blockedReasonsWithChain);
+
+ // Set VPN lockdown
+ final Range<Integer> myUidRange = new Range<>(myUid, myUid);
+ runWithShellPermissionIdentity(() -> setRequireVpnForUids(
+ true /* requireVpn */, List.of(myUidRange)), NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork,
+ blockedReasonsWithChainAndLockDown);
+
+ // Unset VPN lockdown
+ runWithShellPermissionIdentity(() -> setRequireVpnForUids(
+ false /* requireVpn */, List.of(myUidRange)), NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, blockedReasonsWithChain);
+
+ // Set RULE_ALLOW on target chain
+ runWithShellPermissionIdentity(
+ () -> mCm.setUidFirewallRule(chain, myUid, FIREWALL_RULE_ALLOW),
+ NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, blockedReasonsWithoutChain);
+
+ // Set RULE_ALLOW on metered deny chain
+ runWithShellPermissionIdentity(() -> mCm.setUidFirewallRule(
+ FIREWALL_CHAIN_METERED_DENY_USER, myUid, FIREWALL_RULE_ALLOW),
+ NETWORK_SETTINGS);
+ if (metered) {
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, BLOCKED_REASON_NONE);
+ }
+ }, /* cleanup */ () -> {
+ setWifiMeteredStatusAndWait(ssid, oldMeteredValue, false /* waitForValidation */);
+ }, /* cleanup */ () -> {
+ mCm.unregisterNetworkCallback(cb);
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+ try {
+ mCm.setUidFirewallRule(chain, myUid, previousFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ try {
+ mCm.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, myUid,
+ previousMeteredDenyFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }, NETWORK_SETTINGS);
+ });
+ }
+
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testBlockedReasons_setUidFirewallRule() throws Exception {
+ doTestBlockedReasons_setUidFirewallRule(FIREWALL_CHAIN_DOZABLE, true /* metered */);
+ doTestBlockedReasons_setUidFirewallRule(FIREWALL_CHAIN_STANDBY, false /* metered */);
+ }
+
+ private void doTestBlockedReasons_setFirewallChainEnabled(final int chain) {
+ // Store current firewall chains status.
+ final int myUid = Process.myUid();
+ // TODO(b/342508466): Use runAsShell
+ final boolean wasChainEnabled = runWithShellPermissionIdentity(
+ () -> mCm.getFirewallChainEnabled(chain), NETWORK_SETTINGS);
+ final int previousFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(chain, myUid), NETWORK_SETTINGS);
+
+ final DetailedBlockedStatusCallback cb = new DetailedBlockedStatusCallback();
+ networkCallbackRule.registerDefaultNetworkCallback(cb);
+ final Network network = cb.expect(CallbackEntry.AVAILABLE).getNetwork();
+ testAndCleanup(() -> {
+ // Disable chain and set RULE_DENY on target chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ mCm.setUidFirewallRule(chain, myUid, FIREWALL_RULE_DENY);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+
+ // Enable chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+
+ // Disable chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+ try {
+ mCm.setUidFirewallRule(chain, myUid, previousFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }, NETWORK_SETTINGS);
+ });
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testBlockedReasons_setFirewallChainEnabled() {
+ doTestBlockedReasons_setFirewallChainEnabled(FIREWALL_CHAIN_POWERSAVE);
+ doTestBlockedReasons_setFirewallChainEnabled(FIREWALL_CHAIN_OEM_DENY_1);
+ }
+
+ private void doTestBlockedReasons_replaceFirewallChain(
+ final int chain, final boolean isAllowList) {
+ // Store current firewall chains status.
+ final int myUid = Process.myUid();
+ final boolean wasChainEnabled = runWithShellPermissionIdentity(
+ () -> mCm.getFirewallChainEnabled(chain), NETWORK_SETTINGS);
+ final int previousFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(chain, myUid), NETWORK_SETTINGS);
+
+ final DetailedBlockedStatusCallback cb = new DetailedBlockedStatusCallback();
+ networkCallbackRule.registerDefaultNetworkCallback(cb);
+ final Network network = cb.expect(CallbackEntry.AVAILABLE).getNetwork();
+ testAndCleanup(() -> {
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+
+ // Remove uid from the target chain and enable chain
+ runWithShellPermissionIdentity(() -> {
+ // Note that this removes *all* UIDs from the chain, not just the UID that is
+ // being tested. This is probably OK since FIREWALL_CHAIN_OEM_DENY_2 is unused
+ // in AOSP and FIREWALL_CHAIN_BACKGROUND is probably empty when running this
+ // test (since nothing is in the foreground).
+ //
+ // TODO(b/342508466): add a getFirewallUidChainContents or similar method to fetch
+ // chain contents, and update this test to use it.
+ mCm.replaceFirewallChain(chain, new int[0]);
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ }, NETWORK_SETTINGS);
+
+ if (isAllowList) {
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+ } else {
+ cb.assertNoBlockedStatusCallback();
+ }
+
+ // Put uid on the target chain
+ runWithShellPermissionIdentity(
+ () -> mCm.replaceFirewallChain(chain, new int[]{myUid}), NETWORK_SETTINGS);
+
+ if (isAllowList) {
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+ } else {
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+ }
+
+ // Remove uid from the target chain
+ runWithShellPermissionIdentity(
+ () -> mCm.replaceFirewallChain(chain, new int[0]), NETWORK_SETTINGS);
+
+ if (isAllowList) {
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+ } else {
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+ }
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+ try {
+ mCm.setUidFirewallRule(chain, myUid, previousFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }, NETWORK_SETTINGS);
+ });
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testBlockedReasons_replaceFirewallChain() {
+ doTestBlockedReasons_replaceFirewallChain(
+ FIREWALL_CHAIN_BACKGROUND, true /* isAllowChain */);
+ doTestBlockedReasons_replaceFirewallChain(
+ FIREWALL_CHAIN_OEM_DENY_2, false /* isAllowChain */);
+ }
+
private void assumeTestSApis() {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
// shims, and @IgnoreUpTo does not check that.
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index d2e46af..06bdca6 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -27,6 +27,7 @@
import android.net.ConnectivityManager
import android.net.IDnsResolver
import android.net.INetd
+import android.net.INetd.PERMISSION_INTERNET
import android.net.LinkProperties
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
@@ -225,6 +226,9 @@
override fun getSystemProperties() = mock(MockableSystemProperties::class.java)
override fun makeNetIdManager() = TestNetIdManager()
override fun getBpfNetMaps(context: Context?, netd: INetd?) = mock(BpfNetMaps::class.java)
+ .also {
+ doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
+ }
override fun isChangeEnabled(changeId: Long, uid: Int) = true
override fun makeMultinetworkPolicyTracker(
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
index 336be2e..c118d0a 100644
--- a/tests/mts/Android.bp
+++ b/tests/mts/Android.bp
@@ -31,6 +31,8 @@
header_libs: [
"bpf_headers",
],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
static_libs: [
"libbase",
"libmodules-utils-build",
diff --git a/tests/native/connectivity_native_test/Android.bp b/tests/native/connectivity_native_test/Android.bp
index 2f66d17..c5088c6 100644
--- a/tests/native/connectivity_native_test/Android.bp
+++ b/tests/native/connectivity_native_test/Android.bp
@@ -17,8 +17,9 @@
"connectivity_native_test.cpp",
],
header_libs: ["bpf_connectivity_headers"],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
shared_libs: [
- "libbase",
"libbinder_ndk",
"liblog",
"libnetutils",
@@ -26,6 +27,7 @@
],
static_libs: [
"connectivity_native_aidl_interface-lateststable-ndk",
+ "libbase",
"libcutils",
"libmodules-utils-build",
"libutils",
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index b6cb09b..44a8222 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -53,6 +53,7 @@
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_DEVICE_TYPE;
@@ -163,7 +164,6 @@
import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
import static com.android.server.ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK;
-import static com.android.server.ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION;
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
import static com.android.server.ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS;
import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
@@ -178,6 +178,7 @@
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
import static com.android.server.connectivity.ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN;
+import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.ConcurrentUtils.await;
@@ -1746,7 +1747,15 @@
private void setBlockedReasonChanged(int blockedReasons) {
mBlockedReasons = blockedReasons;
- mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
+ if (mDeps.isAtLeastV()) {
+ visibleOnHandlerThread(mCsHandlerThread.getThreadHandler(),
+ () -> mService.handleBlockedReasonsChanged(
+ List.of(new Pair<>(Process.myUid(), blockedReasons))
+
+ ));
+ } else {
+ mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
+ }
}
private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) {
@@ -1927,11 +1936,16 @@
mService.mLingerDelayMs = TEST_LINGER_DELAY_MS;
mService.mNascentDelayMs = TEST_NASCENT_DELAY_MS;
- final ArgumentCaptor<NetworkPolicyCallback> policyCallbackCaptor =
- ArgumentCaptor.forClass(NetworkPolicyCallback.class);
- verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(),
- policyCallbackCaptor.capture());
- mPolicyCallback = policyCallbackCaptor.getValue();
+ if (mDeps.isAtLeastV()) {
+ verify(mNetworkPolicyManager, never()).registerNetworkPolicyCallback(any(), any());
+ mPolicyCallback = null;
+ } else {
+ final ArgumentCaptor<NetworkPolicyCallback> policyCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkPolicyCallback.class);
+ verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(),
+ policyCallbackCaptor.capture());
+ mPolicyCallback = policyCallbackCaptor.getValue();
+ }
// Create local CM before sending system ready so that we can answer
// getSystemService() correctly.
@@ -2169,8 +2183,6 @@
return true;
case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
return true;
- case DELAY_DESTROY_FROZEN_SOCKETS_VERSION:
- return true;
default:
return super.isFeatureEnabled(context, name);
}
@@ -2187,6 +2199,8 @@
return true;
case BACKGROUND_FIREWALL_CHAIN:
return true;
+ case DELAY_DESTROY_SOCKETS:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
@@ -7527,13 +7541,13 @@
@Test
public void testNetworkCallbackMaximum() throws Exception {
final int MAX_REQUESTS = 100;
- final int CALLBACKS = 87;
+ final int CALLBACKS = 88;
final int DIFF_INTENTS = 10;
final int SAME_INTENTS = 10;
final int SYSTEM_ONLY_MAX_REQUESTS = 250;
- // Assert 1 (Default request filed before testing) + CALLBACKS + DIFF_INTENTS +
- // 1 (same intent) = MAX_REQUESTS - 1, since the capacity is MAX_REQUEST - 1.
- assertEquals(MAX_REQUESTS - 1, 1 + CALLBACKS + DIFF_INTENTS + 1);
+ // CALLBACKS + DIFF_INTENTS + 1 (same intent)
+ // = MAX_REQUESTS - 1, since the capacity is MAX_REQUEST - 1.
+ assertEquals(MAX_REQUESTS - 1, CALLBACKS + DIFF_INTENTS + 1);
NetworkRequest networkRequest = new NetworkRequest.Builder().build();
ArrayList<Object> registered = new ArrayList<>();
@@ -9867,6 +9881,28 @@
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellAgent);
+ // Remove PERMISSION_INTERNET and disable NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
+ doReturn(INetd.PERMISSION_NONE).when(mBpfNetMaps).getNetPermForUid(Process.myUid());
+ mDeps.setChangeIdEnabled(false,
+ NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, Process.myUid());
+
+ setBlockedReasonChanged(BLOCKED_REASON_DOZE);
+ if (mDeps.isAtLeastV()) {
+ // On V+, network access from app that does not have INTERNET permission is considered
+ // not blocked if NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION is disabled.
+ // So blocked status does not change from BLOCKED_REASON_NONE
+ cellNetworkCallback.assertNoCallback();
+ detailedCallback.assertNoCallback();
+ } else {
+ // On U-, onBlockedStatusChanged callback is called with blocked reasons CS receives
+ // from NPMS callback regardless of permission app has.
+ // Note that this cannot actually happen because on U-, NPMS will never notify any
+ // blocked reasons for apps that don't have the INTERNET permission.
+ cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> cb.getBlocked());
+ detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+ cb -> cb.getReason() == BLOCKED_REASON_DOZE);
+ }
+
mCm.unregisterNetworkCallback(cellNetworkCallback);
}
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 44512bb..ea3d2dd 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -29,8 +29,6 @@
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
-import static com.android.testutils.MiscAsserts.assertContainsExactly;
-import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
import static org.junit.Assert.assertEquals;
@@ -38,12 +36,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivitySettingsManager;
@@ -74,7 +72,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -122,29 +119,6 @@
assertFieldCountEquals(3, ResolverOptionsParcel.class);
}
- private void assertResolverParamsEquals(@NonNull ResolverParamsParcel actual,
- @NonNull ResolverParamsParcel expected) {
- assertEquals(actual.netId, expected.netId);
- assertEquals(actual.sampleValiditySeconds, expected.sampleValiditySeconds);
- assertEquals(actual.successThreshold, expected.successThreshold);
- assertEquals(actual.minSamples, expected.minSamples);
- assertEquals(actual.maxSamples, expected.maxSamples);
- assertEquals(actual.baseTimeoutMsec, expected.baseTimeoutMsec);
- assertEquals(actual.retryCount, expected.retryCount);
- assertContainsStringsExactly(actual.servers, expected.servers);
- assertContainsStringsExactly(actual.domains, expected.domains);
- assertEquals(actual.tlsName, expected.tlsName);
- assertContainsStringsExactly(actual.tlsServers, expected.tlsServers);
- assertContainsStringsExactly(actual.tlsFingerprints, expected.tlsFingerprints);
- assertEquals(actual.caCertificate, expected.caCertificate);
- assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs);
- assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
- assertContainsExactly(actual.transportTypes, expected.transportTypes);
- assertEquals(actual.meteredNetwork, expected.meteredNetwork);
- assertEquals(actual.dohParams, expected.dohParams);
- assertFieldCountEquals(18, ResolverParamsParcel.class);
- }
-
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -365,11 +339,6 @@
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
mDnsManager.flushVmDnsCache();
- final ArgumentCaptor<ResolverParamsParcel> resolverParamsParcelCaptor =
- ArgumentCaptor.forClass(ResolverParamsParcel.class);
- verify(mMockDnsResolver, times(1)).setResolverConfiguration(
- resolverParamsParcelCaptor.capture());
- final ResolverParamsParcel actualParams = resolverParamsParcelCaptor.getValue();
final ResolverParamsParcel expectedParams = new ResolverParamsParcel();
expectedParams.netId = TEST_NETID;
expectedParams.sampleValiditySeconds = TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS;
@@ -384,7 +353,8 @@
expectedParams.resolverOptions = null;
expectedParams.meteredNetwork = true;
expectedParams.dohParams = null;
- assertResolverParamsEquals(actualParams, expectedParams);
+ expectedParams.interfaceNames = new String[]{TEST_IFACENAME};
+ verify(mMockDnsResolver, times(1)).setResolverConfiguration(eq(expectedParams));
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
new file mode 100644
index 0000000..0590fbb
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
+import android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED
+import android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND
+import android.net.ConnectivityManager.BLOCKED_REASON_DOZE
+import android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED
+import android.net.ConnectivityManager.BLOCKED_REASON_NONE
+import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
+import android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE
+import android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER
+import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
+import android.net.ConnectivityManager.FIREWALL_RULE_DENY
+import android.net.ConnectivitySettingsManager
+import android.net.INetd.PERMISSION_NONE
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
+import android.os.Build
+import android.os.Process
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatusInt
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mockito.doReturn
+
+private fun cellNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+private fun cellRequest() = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .build()
+private fun wifiNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .build()
+private fun wifiRequest() = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build()
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CSBlockedReasonsTest : CSTest() {
+
+ inner class DetailedBlockedStatusCallback : TestableNetworkCallback() {
+ override fun onBlockedStatusChanged(network: Network, blockedReasons: Int) {
+ history.add(BlockedStatusInt(network, blockedReasons))
+ }
+
+ fun expectBlockedStatusChanged(network: Network, blockedReasons: Int) {
+ expect<BlockedStatusInt>(network) { it.reason == blockedReasons }
+ }
+ }
+
+ @Test
+ fun testBlockedReasons_onAvailable() {
+ doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_DATA_SAVER)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ cellCb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_DATA_SAVER
+ )
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE
+ )
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_dataSaverChanged() {
+ doReturn(BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ doReturn(true).`when`(netd).bandwidthEnableDataSaver(anyBoolean())
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ cellCb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER
+ )
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_APP_BACKGROUND
+ )
+
+ // Disable data saver
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setDataSaverEnabled(false)
+ cellCb.expectBlockedStatusChanged(cellAgent.network, BLOCKED_REASON_APP_BACKGROUND)
+
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
+ // method on this callback was called, but it does not guarantee that ConnectivityService
+ // has finished processing all onBlockedStatusChanged callbacks for all requests.
+ waitForIdle()
+ // Enable data saver
+ doReturn(BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setDataSaverEnabled(true)
+ cellCb.expectBlockedStatusChanged(
+ cellAgent.network,
+ BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER
+ )
+ // BlockedStatus does not change for the non-metered network
+ wifiCb.assertNoCallback()
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_setUidFirewallRule() {
+ doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ cellCb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED
+ )
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE
+ )
+
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
+ // method on this callback was called, but it does not guarantee that ConnectivityService
+ // has finished processing all onBlockedStatusChanged callbacks for all requests.
+ waitForIdle()
+ // Set RULE_ALLOW on metered deny chain
+ doReturn(BLOCKED_REASON_DOZE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_METERED_DENY_USER,
+ Process.myUid(),
+ FIREWALL_RULE_ALLOW
+ )
+ cellCb.expectBlockedStatusChanged(
+ cellAgent.network,
+ BLOCKED_REASON_DOZE
+ )
+ // BlockedStatus does not change for the non-metered network
+ wifiCb.assertNoCallback()
+
+ // Set RULE_DENY on metered deny chain
+ doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_METERED_DENY_USER,
+ Process.myUid(),
+ FIREWALL_RULE_DENY
+ )
+ cellCb.expectBlockedStatusChanged(
+ cellAgent.network,
+ BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED
+ )
+ // BlockedStatus does not change for the non-metered network
+ wifiCb.assertNoCallback()
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_setFirewallChainEnabled() {
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(wifiRequest(), wifiCb)
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_NONE
+ )
+
+ // Enable dozable firewall chain
+ doReturn(BLOCKED_REASON_DOZE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, true)
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_DOZE
+ )
+
+ // Disable dozable firewall chain
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, false)
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NONE
+ )
+
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_replaceFirewallChain() {
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(wifiRequest(), wifiCb)
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_APP_BACKGROUND
+ )
+
+ // Put uid on background firewall chain
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf(Process.myUid()))
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NONE
+ )
+
+ // Remove uid from background firewall chain
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf())
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_APP_BACKGROUND
+ )
+
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_perAppDefaultNetwork() {
+ doReturn(BLOCKED_METERED_REASON_USER_RESTRICTED)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+
+ val cb = DetailedBlockedStatusCallback()
+ cm.registerDefaultNetworkCallback(cb)
+ cb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_NONE
+ )
+
+ // CS must send correct blocked reasons after per app default network change
+ ConnectivitySettingsManager.setMobileDataPreferredUids(context, setOf(Process.myUid()))
+ service.updateMobileDataPreferredUids()
+ cb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_METERED_REASON_USER_RESTRICTED
+ )
+
+ // Remove per app default network request
+ ConnectivitySettingsManager.setMobileDataPreferredUids(context, setOf())
+ service.updateMobileDataPreferredUids()
+ cb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_NONE
+ )
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ private fun doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission: Boolean) {
+ doReturn(PERMISSION_NONE).`when`(bpfNetMaps).getNetPermForUid(Process.myUid())
+
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(wifiRequest(), wifiCb)
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ val expectedBlockedReason = if (blockedByNoInternetPermission) {
+ BLOCKED_REASON_NETWORK_RESTRICTED
+ } else {
+ BLOCKED_REASON_NONE
+ }
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = expectedBlockedReason
+ )
+
+ // Enable background firewall chain
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true)
+ if (blockedByNoInternetPermission) {
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NETWORK_RESTRICTED or BLOCKED_REASON_APP_BACKGROUND
+ )
+ }
+
+ // Disable background firewall chain
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
+ if (blockedByNoInternetPermission) {
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NETWORK_RESTRICTED
+ )
+ } else {
+ // No callback is expected since blocked reasons does not change from
+ // BLOCKED_REASON_NONE.
+ wifiCb.assertNoCallback()
+ }
+ }
+
+ @Test
+ fun testBlockedReasonsNoInternetPermission_changeDisabled() {
+ deps.setChangeIdEnabled(false, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
+ doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = false)
+ }
+
+ @Test
+ fun testBlockedReasonsNoInternetPermission_changeEnabled() {
+ deps.setChangeIdEnabled(true, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
+ doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = true)
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt
new file mode 100644
index 0000000..bc5be78
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.app.ActivityManager.UidFrozenStateChangedCallback
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN
+import android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND
+import android.net.ConnectivityManager.BLOCKED_REASON_NONE
+import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
+import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
+import android.net.ConnectivityManager.FIREWALL_RULE_DENY
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.os.Build
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener
+import com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+private const val TIMESTAMP = 1234L
+private const val TEST_UID = 1234
+private const val TEST_UID2 = 5678
+private const val TEST_CELL_IFACE = "test_rmnet"
+
+private fun cellNc() = NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+
+private fun cellLp() = LinkProperties().also{
+ it.interfaceName = TEST_CELL_IFACE
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CSDestroySocketTest : CSTest() {
+ private fun getRegisteredNetdUnsolicitedEventListener(): BaseNetdUnsolicitedEventListener {
+ val captor = ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener::class.java)
+ verify(netd).registerUnsolicitedEventListener(captor.capture())
+ return captor.value
+ }
+
+ private fun getUidFrozenStateChangedCallback(): UidFrozenStateChangedCallback {
+ val captor = ArgumentCaptor.forClass(UidFrozenStateChangedCallback::class.java)
+ verify(activityManager).registerUidFrozenStateChangedCallback(any(), captor.capture())
+ return captor.value
+ }
+
+ private fun doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork: Boolean,
+ expectDelay: Boolean
+ ) {
+ val netdEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val inOrder = inOrder(destroySocketsWrapper)
+
+ val cellAgent = Agent(nc = cellNc(), lp = cellLp())
+ cellAgent.connect()
+ if (restrictionWithIdleNetwork) {
+ // Make cell default network idle
+ netdEventListener.onInterfaceClassActivityChanged(
+ false, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ }
+
+ // Set deny rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_DENY
+ )
+ waitForIdle()
+ if (expectDelay) {
+ inOrder.verify(destroySocketsWrapper, never())
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ } else {
+ inOrder.verify(destroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ }
+
+ netdEventListener.onInterfaceClassActivityChanged(
+ true, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ waitForIdle()
+ if (expectDelay) {
+ inOrder.verify(destroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ } else {
+ inOrder.verify(destroySocketsWrapper, never())
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ }
+
+ cellAgent.disconnect()
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, true)])
+ fun testBackgroundAppDestroySockets() {
+ doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork = true,
+ expectDelay = true
+ )
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, true)])
+ fun testBackgroundAppDestroySockets_activeNetwork() {
+ doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork = false,
+ expectDelay = false
+ )
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, false)])
+ fun testBackgroundAppDestroySockets_featureIsDisabled() {
+ doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork = true,
+ expectDelay = false
+ )
+ }
+
+ @Test
+ fun testReplaceFirewallChain() {
+ val netdEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val inOrder = inOrder(destroySocketsWrapper)
+
+ val cellAgent = Agent(nc = cellNc(), lp = cellLp())
+ cellAgent.connect()
+ // Make cell default network idle
+ netdEventListener.onInterfaceClassActivityChanged(
+ false, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+
+ // Set allow rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_ALLOW
+ )
+ // Set deny rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID2)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID2,
+ FIREWALL_RULE_DENY
+ )
+
+ // Put only TEST_UID2 on background chain (deny TEST_UID and allow TEST_UID2)
+ doReturn(setOf(TEST_UID))
+ .`when`(bpfNetMaps).getUidsWithAllowRuleOnAllowListChain(FIREWALL_CHAIN_BACKGROUND)
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID2)
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf(TEST_UID2))
+ waitForIdle()
+ inOrder.verify(destroySocketsWrapper, never())
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+
+ netdEventListener.onInterfaceClassActivityChanged(
+ true, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ waitForIdle()
+ inOrder.verify(destroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+
+ cellAgent.disconnect()
+ }
+
+ private fun doTestDestroySockets(
+ isFrozen: Boolean,
+ denyOnBackgroundChain: Boolean,
+ enableBackgroundChain: Boolean,
+ expectDestroySockets: Boolean
+ ) {
+ val netdEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val frozenStateCallback = getUidFrozenStateChangedCallback()
+
+ // Make cell default network idle
+ val cellAgent = Agent(nc = cellNc(), lp = cellLp())
+ cellAgent.connect()
+ netdEventListener.onInterfaceClassActivityChanged(
+ false, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+
+ // Set deny rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_DENY
+ )
+
+ // Freeze TEST_UID
+ frozenStateCallback.onUidFrozenStateChanged(
+ intArrayOf(TEST_UID),
+ intArrayOf(UID_FROZEN_STATE_FROZEN)
+ )
+
+ if (!isFrozen) {
+ // Unfreeze TEST_UID
+ frozenStateCallback.onUidFrozenStateChanged(
+ intArrayOf(TEST_UID),
+ intArrayOf(UID_FROZEN_STATE_UNFROZEN)
+ )
+ }
+ if (!enableBackgroundChain) {
+ // Disable background chain
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
+ }
+ if (!denyOnBackgroundChain) {
+ // Set allow rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_ALLOW
+ )
+ }
+ verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+
+ // Make cell network active
+ netdEventListener.onInterfaceClassActivityChanged(
+ true, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ waitForIdle()
+
+ if (expectDestroySockets) {
+ verify(destroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ } else {
+ verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ }
+ }
+
+ @Test
+ fun testDestroySockets_backgroundDeny_frozen() {
+ doTestDestroySockets(
+ isFrozen = true,
+ denyOnBackgroundChain = true,
+ enableBackgroundChain = true,
+ expectDestroySockets = true
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundDeny_nonFrozen() {
+ doTestDestroySockets(
+ isFrozen = false,
+ denyOnBackgroundChain = true,
+ enableBackgroundChain = true,
+ expectDestroySockets = true
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundAllow_frozen() {
+ doTestDestroySockets(
+ isFrozen = true,
+ denyOnBackgroundChain = false,
+ enableBackgroundChain = true,
+ expectDestroySockets = true
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundAllow_nonFrozen() {
+ // If the app is neither frozen nor under background restriction, sockets are not
+ // destroyed
+ doTestDestroySockets(
+ isFrozen = false,
+ denyOnBackgroundChain = false,
+ enableBackgroundChain = true,
+ expectDestroySockets = false
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundChainDisabled_nonFrozen() {
+ // If the app is neither frozen nor under background restriction, sockets are not
+ // destroyed
+ doTestDestroySockets(
+ isFrozen = false,
+ denyOnBackgroundChain = true,
+ enableBackgroundChain = false,
+ expectDestroySockets = false
+ )
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
index bb7fb51..93f6e81 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
@@ -37,6 +37,7 @@
import com.android.testutils.TestableNetworkCallback
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.InOrder
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.timeout
@@ -96,6 +97,11 @@
private val LOCAL_IPV6_ADDRRESS = InetAddresses.parseNumericAddress("fe80::1234")
private val LOCAL_IPV6_LINK_ADDRRESS = LinkAddress(LOCAL_IPV6_ADDRRESS, 64)
+ fun verifyNoMoreIngressDiscardRuleChange(inorder: InOrder) {
+ inorder.verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+ inorder.verify(bpfNetMaps, never()).removeIngressDiscardRule(any())
+ }
+
@Test
fun testVpnIngressDiscardRule_UpdateVpnAddress() {
// non-VPN network whose address will be not duplicated with VPN address
@@ -148,7 +154,7 @@
// IngressDiscardRule is added to the VPN address
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
// The VPN interface name is changed
val newlp = lp(VPN_IFNAME2, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
@@ -157,7 +163,7 @@
// IngressDiscardRule is updated with the new interface name
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME2)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
agent.disconnect()
inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS)
@@ -206,10 +212,10 @@
// IngressDiscardRule for IPV6_ADDRESS2 is removed but IngressDiscardRule for
// IPV6_LINK_ADDRESS is not added since Wi-Fi also uses IPV6_LINK_ADDRESS
inorder.verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS2)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
vpnAgent.disconnect()
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
cm.unregisterNetworkCallback(cb)
}
@@ -225,7 +231,7 @@
// IngressDiscardRule is added to the VPN address
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
val nr = nr(TRANSPORT_WIFI)
val cb = TestableNetworkCallback()
@@ -247,7 +253,7 @@
// IngressDiscardRule is added to the VPN address since the VPN address is not duplicated
// with the Wi-Fi address
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
// The Wi-Fi address is changed back to the same address as the VPN interface
wifiAgent.sendLinkProperties(wifiLp)
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 3b06ad0..47a6763 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -27,6 +27,7 @@
import android.content.res.Resources
import android.net.ConnectivityManager
import android.net.INetd
+import android.net.INetd.PERMISSION_INTERNET
import android.net.InetAddresses
import android.net.LinkProperties
import android.net.LocalNetworkConfig
@@ -89,6 +90,7 @@
import org.junit.Rule
import org.junit.rules.TestName
import org.mockito.AdditionalAnswers.delegatesTo
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
@@ -157,11 +159,11 @@
it[ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER] = true
it[ConnectivityFlags.REQUEST_RESTRICTED_WIFI] = true
it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
- it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
it[ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK] = true
it[ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING] = true
it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true
+ it[ConnectivityFlags.DELAY_DESTROY_SOCKETS] = true
}
fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
@@ -171,7 +173,11 @@
val contentResolver = makeMockContentResolver(context)
val PRIMARY_USER = 0
- val PRIMARY_USER_INFO = UserInfo(PRIMARY_USER, "" /* name */, UserInfo.FLAG_PRIMARY)
+ val PRIMARY_USER_INFO = UserInfo(
+ PRIMARY_USER,
+ "", // name
+ UserInfo.FLAG_PRIMARY
+ )
val PRIMARY_USER_HANDLE = UserHandle(PRIMARY_USER)
val userManager = makeMockUserManager(PRIMARY_USER_INFO, PRIMARY_USER_HANDLE)
val activityManager = makeActivityManager()
@@ -183,10 +189,16 @@
val connResources = makeMockConnResources(sysResources, packageManager)
val netd = mock<INetd>()
- val bpfNetMaps = mock<BpfNetMaps>()
+ val bpfNetMaps = mock<BpfNetMaps>().also {
+ doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
+ }
val clatCoordinator = mock<ClatCoordinator>()
val networkRequestStateStatsMetrics = mock<NetworkRequestStateStatsMetrics>()
- val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
+ val proxyTracker = ProxyTracker(
+ context,
+ mock<Handler>(),
+ 16 // EVENT_PROXY_HAS_CHANGED
+ )
val systemConfigManager = makeMockSystemConfigManager()
val batteryStats = mock<IBatteryStats>()
val batteryManager = BatteryStatsManager(batteryStats)
@@ -198,6 +210,7 @@
val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
val satelliteAccessController = mock<SatelliteAccessController>()
+ val destroySocketsWrapper = mock<DestroySocketsWrapper>()
val deps = CSDeps()
@@ -251,6 +264,11 @@
alarmHandlerThread.join()
}
+ // Class to be mocked and used to verify destroy sockets methods call
+ open inner class DestroySocketsWrapper {
+ open fun destroyLiveTcpSocketsByOwnerUids(ownerUids: Set<Int>) {}
+ }
+
inner class CSDeps : ConnectivityService.Dependencies() {
override fun getResources(ctx: Context) = connResources
override fun getBpfNetMaps(context: Context, netd: INetd) = this@CSTest.bpfNetMaps
@@ -356,6 +374,11 @@
override fun getCallingUid() =
if (callingUid == CALLING_UID_UNMOCKED) super.getCallingUid() else callingUid
+
+ override fun destroyLiveTcpSocketsByOwnerUids(ownerUids: Set<Int>) {
+ // Call mocked destroyLiveTcpSocketsByOwnerUids so that test can verify this method call
+ destroySocketsWrapper.destroyLiveTcpSocketsByOwnerUids(ownerUids)
+ }
}
inner class CSContext(base: Context) : BroadcastInterceptingContext(base) {
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 6425daa..9026481 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -314,7 +314,7 @@
new SetFeatureFlagsRule((name, enabled) -> {
mFeatureFlags.put(name, enabled);
return null;
- });
+ }, (name) -> mFeatureFlags.getOrDefault(name, false));
private class MockContext extends BroadcastInterceptingContext {
private final Context mBaseContext;
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 0c200fd..6a9ae71 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -105,6 +105,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
@@ -131,6 +132,8 @@
import java.net.Inet6Address;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
+import java.time.Clock;
+import java.time.DateTimeException;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
@@ -465,6 +468,7 @@
private void setEnabledInternal(
boolean isEnabled, boolean persist, @NonNull OperationReceiverWrapper receiver) {
+ checkOnHandlerThread();
if (isEnabled && isThreadUserRestricted()) {
receiver.onError(
ERROR_FAILED_PRECONDITION,
@@ -619,7 +623,10 @@
return !mForceStopOtDaemonEnabled
&& !mUserRestricted
- && (!mAirplaneModeOn || enabledInAirplaneMode)
+ // FIXME(b/340744397): Note that here we need to call `isAirplaneModeOn()` to get
+ // the latest state of airplane mode but can't use `mIsAirplaneMode`. This is for
+ // avoiding the race conditions described in b/340744397
+ && (!isAirplaneModeOn() || enabledInAirplaneMode)
&& mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
}
@@ -820,7 +827,6 @@
networkName,
supportedChannelMask,
preferredChannelMask,
- Instant.now(),
new Random(),
new SecureRandom());
@@ -838,9 +844,18 @@
String networkName,
int supportedChannelMask,
int preferredChannelMask,
- Instant now,
Random random,
SecureRandom secureRandom) {
+ boolean authoritative = false;
+ Instant now = Instant.now();
+ try {
+ Clock clock = SystemClock.currentNetworkTimeClock();
+ now = clock.instant();
+ authoritative = true;
+ } catch (DateTimeException e) {
+ Log.w(TAG, "Failed to get authoritative time", e);
+ }
+
int panId = random.nextInt(/* bound= */ 0xffff);
final byte[] meshLocalPrefix = newRandomBytes(random, LENGTH_MESH_LOCAL_PREFIX_BITS / 8);
meshLocalPrefix[0] = MESH_LOCAL_PREFIX_FIRST_BYTE;
@@ -854,7 +869,7 @@
return new ActiveOperationalDataset.Builder()
.setActiveTimestamp(
new OperationalDatasetTimestamp(
- now.getEpochSecond() & 0xffffffffffffL, 0, false))
+ now.getEpochSecond() & 0xffffffffffffL, 0, authoritative))
.setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
.setPanId(panId)
.setNetworkName(networkName)
@@ -960,7 +975,11 @@
private void checkOnHandlerThread() {
if (Looper.myLooper() != mHandler.getLooper()) {
- Log.wtf(TAG, "Must be on the handler thread!");
+ throw new IllegalStateException(
+ "Not running on ThreadNetworkControllerService thread ("
+ + mHandler.getLooper()
+ + ") : "
+ + Looper.myLooper());
}
}
@@ -1058,7 +1077,7 @@
}
@Override
- public void leave(@NonNull IOperationReceiver receiver) throws RemoteException {
+ public void leave(@NonNull IOperationReceiver receiver) {
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
mHandler.post(() -> leaveInternal(new OperationReceiverWrapper(receiver)));
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
index 30c67ca..4c22278 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
@@ -72,7 +72,10 @@
// PHASE_ACTIVITY_MANAGER_READY and PHASE_THIRD_PARTY_APPS_CAN_START
mCountryCode.initialize();
mShellCommand =
- new ThreadNetworkShellCommand(requireNonNull(mControllerService), mCountryCode);
+ new ThreadNetworkShellCommand(
+ mContext,
+ requireNonNull(mControllerService),
+ requireNonNull(mCountryCode));
}
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
index c6a1618..54155ee 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
@@ -16,50 +16,57 @@
package com.android.server.thread;
-import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
+import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IOperationReceiver;
+import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.PendingOperationalDataset;
import android.net.thread.ThreadNetworkException;
-import android.os.Binder;
-import android.os.Process;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.net.module.util.HexDump;
import java.io.PrintWriter;
import java.time.Duration;
-import java.util.List;
+import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
- * Interprets and executes 'adb shell cmd thread_network [args]'.
+ * Interprets and executes 'adb shell cmd thread_network <subcommand>'.
+ *
+ * <p>Subcommands which don't have an equivalent Java API now require the
+ * "android.permission.THREAD_NETWORK_TESTING" permission. For a specific subcommand, it also
+ * requires the same permissions of the equivalent Java / AIDL API.
*
* <p>To add new commands: - onCommand: Add a case "<command>" execute. Return a 0 if command
* executed successfully. - onHelp: add a description string.
- *
- * <p>Permissions: currently root permission is required for some commands. Others will enforce the
- * corresponding API permissions.
*/
-public class ThreadNetworkShellCommand extends BasicShellCommandHandler {
+public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
private static final Duration SET_ENABLED_TIMEOUT = Duration.ofSeconds(2);
+ private static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
+ private static final Duration MIGRATE_TIMEOUT = Duration.ofSeconds(2);
private static final Duration FORCE_STOP_TIMEOUT = Duration.ofSeconds(1);
+ private static final String PERMISSION_THREAD_NETWORK_TESTING =
+ "android.permission.THREAD_NETWORK_TESTING";
- // These don't require root access.
- private static final List<String> NON_PRIVILEGED_COMMANDS =
- List.of("help", "get-country-code", "enable", "disable");
+ private final Context mContext;
+ private final ThreadNetworkControllerService mControllerService;
+ private final ThreadNetworkCountryCode mCountryCode;
- @NonNull private final ThreadNetworkControllerService mControllerService;
- @NonNull private final ThreadNetworkCountryCode mCountryCode;
@Nullable private PrintWriter mOutputWriter;
@Nullable private PrintWriter mErrorWriter;
- ThreadNetworkShellCommand(
- @NonNull ThreadNetworkControllerService controllerService,
- @NonNull ThreadNetworkCountryCode countryCode) {
+ public ThreadNetworkShellCommand(
+ Context context,
+ ThreadNetworkControllerService controllerService,
+ ThreadNetworkCountryCode countryCode) {
+ mContext = context;
mControllerService = controllerService;
mCountryCode = countryCode;
}
@@ -79,79 +86,120 @@
}
@Override
+ public void onHelp() {
+ final PrintWriter pw = getOutputWriter();
+ pw.println("Thread network commands:");
+ pw.println(" help or -h");
+ pw.println(" Print this help text.");
+ pw.println(" enable");
+ pw.println(" Enables Thread radio");
+ pw.println(" disable");
+ pw.println(" Disables Thread radio");
+ pw.println(" join <active-dataset-tlvs>");
+ pw.println(" Joins a network of the given dataset");
+ pw.println(" migrate <active-dataset-tlvs> <delay-seconds>");
+ pw.println(" Migrate to the given network by a specific delay");
+ pw.println(" leave");
+ pw.println(" Leave the current network and erase datasets");
+ pw.println(" force-stop-ot-daemon enabled | disabled ");
+ pw.println(" force stop ot-daemon service");
+ pw.println(" get-country-code");
+ pw.println(" Gets country code as a two-letter string");
+ pw.println(" force-country-code enabled <two-letter code> | disabled ");
+ pw.println(" Sets country code to <two-letter code> or left for normal value");
+ }
+
+ @Override
public int onCommand(String cmd) {
- // Treat no command as help command.
+ // Treat no command as the "help" command
if (TextUtils.isEmpty(cmd)) {
cmd = "help";
}
- final PrintWriter pw = getOutputWriter();
- final PrintWriter perr = getErrorWriter();
-
- // Explicit exclusion from root permission
- if (!NON_PRIVILEGED_COMMANDS.contains(cmd)) {
- final int uid = Binder.getCallingUid();
-
- if (uid != Process.ROOT_UID) {
- perr.println(
- "Uid "
- + uid
- + " does not have access to "
- + cmd
- + " thread command "
- + "(or such command doesn't exist)");
- return -1;
- }
- }
-
switch (cmd) {
case "enable":
return setThreadEnabled(true);
case "disable":
return setThreadEnabled(false);
+ case "join":
+ return join();
+ case "leave":
+ return leave();
+ case "migrate":
+ return migrate();
case "force-stop-ot-daemon":
return forceStopOtDaemon();
case "force-country-code":
- boolean enabled;
- try {
- enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
- } catch (IllegalArgumentException e) {
- perr.println("Invalid argument: " + e.getMessage());
- return -1;
- }
-
- if (enabled) {
- String countryCode = getNextArgRequired();
- if (!ThreadNetworkCountryCode.isValidCountryCode(countryCode)) {
- perr.println(
- "Invalid argument: Country code must be a 2-Character"
- + " string. But got country code "
- + countryCode
- + " instead");
- return -1;
- }
- mCountryCode.setOverrideCountryCode(countryCode);
- pw.println("Set Thread country code: " + countryCode);
-
- } else {
- mCountryCode.clearOverrideCountryCode();
- }
- return 0;
+ return forceCountryCode();
case "get-country-code":
- pw.println("Thread country code = " + mCountryCode.getCountryCode());
- return 0;
+ return getCountryCode();
default:
return handleDefaultCommands(cmd);
}
}
+ private void ensureTestingPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ PERMISSION_THREAD_NETWORK_TESTING,
+ "Permission " + PERMISSION_THREAD_NETWORK_TESTING + " is missing!");
+ }
+
private int setThreadEnabled(boolean enabled) {
CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
mControllerService.setEnabled(enabled, newOperationReceiver(setEnabledFuture));
- return waitForFuture(setEnabledFuture, FORCE_STOP_TIMEOUT, getErrorWriter());
+ return waitForFuture(setEnabledFuture, SET_ENABLED_TIMEOUT, getErrorWriter());
+ }
+
+ private int join() {
+ byte[] datasetTlvs = HexDump.hexStringToByteArray(getNextArgRequired());
+ ActiveOperationalDataset dataset;
+ try {
+ dataset = ActiveOperationalDataset.fromThreadTlvs(datasetTlvs);
+ } catch (IllegalArgumentException e) {
+ getErrorWriter().println("Invalid dataset argument: " + e.getMessage());
+ return -1;
+ }
+ // Do not wait for join to complete because this can take 8 to 30 seconds
+ mControllerService.join(dataset, new IOperationReceiver.Default());
+ return 0;
+ }
+
+ private int leave() {
+ CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+ mControllerService.leave(newOperationReceiver(leaveFuture));
+ return waitForFuture(leaveFuture, LEAVE_TIMEOUT, getErrorWriter());
+ }
+
+ private int migrate() {
+ byte[] datasetTlvs = HexDump.hexStringToByteArray(getNextArgRequired());
+ ActiveOperationalDataset dataset;
+ try {
+ dataset = ActiveOperationalDataset.fromThreadTlvs(datasetTlvs);
+ } catch (IllegalArgumentException e) {
+ getErrorWriter().println("Invalid dataset argument: " + e.getMessage());
+ return -1;
+ }
+
+ int delaySeconds;
+ try {
+ delaySeconds = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ getErrorWriter().println("Invalid delay argument: " + e.getMessage());
+ return -1;
+ }
+
+ PendingOperationalDataset pendingDataset =
+ new PendingOperationalDataset(
+ dataset,
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(delaySeconds));
+ CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
+ mControllerService.scheduleMigration(pendingDataset, newOperationReceiver(migrateFuture));
+ return waitForFuture(migrateFuture, MIGRATE_TIMEOUT, getErrorWriter());
}
private int forceStopOtDaemon() {
+ ensureTestingPermission();
final PrintWriter errorWriter = getErrorWriter();
boolean enabled;
try {
@@ -166,6 +214,40 @@
return waitForFuture(forceStopFuture, FORCE_STOP_TIMEOUT, getErrorWriter());
}
+ private int forceCountryCode() {
+ ensureTestingPermission();
+ final PrintWriter perr = getErrorWriter();
+ boolean enabled;
+ try {
+ enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+ } catch (IllegalArgumentException e) {
+ perr.println("Invalid argument: " + e.getMessage());
+ return -1;
+ }
+
+ if (enabled) {
+ String countryCode = getNextArgRequired();
+ if (!ThreadNetworkCountryCode.isValidCountryCode(countryCode)) {
+ perr.println(
+ "Invalid argument: Country code must be a 2-letter"
+ + " string. But got country code "
+ + countryCode
+ + " instead");
+ return -1;
+ }
+ mCountryCode.setOverrideCountryCode(countryCode);
+ } else {
+ mCountryCode.clearOverrideCountryCode();
+ }
+ return 0;
+ }
+
+ private int getCountryCode() {
+ ensureTestingPermission();
+ getOutputWriter().println("Thread country code = " + mCountryCode.getCountryCode());
+ return 0;
+ }
+
private static IOperationReceiver newOperationReceiver(CompletableFuture<Void> future) {
return new IOperationReceiver.Stub() {
@Override
@@ -224,33 +306,4 @@
String nextArg = getNextArgRequired();
return argTrueOrFalse(nextArg, trueString, falseString);
}
-
- private void onHelpNonPrivileged(PrintWriter pw) {
- pw.println(" enable");
- pw.println(" Enables Thread radio");
- pw.println(" disable");
- pw.println(" Disables Thread radio");
- pw.println(" get-country-code");
- pw.println(" Gets country code as a two-letter string");
- }
-
- private void onHelpPrivileged(PrintWriter pw) {
- pw.println(" force-country-code enabled <two-letter code> | disabled ");
- pw.println(" Sets country code to <two-letter code> or left for normal value");
- pw.println(" force-stop-ot-daemon enabled | disabled ");
- pw.println(" force stop ot-daemon service");
- }
-
- @Override
- public void onHelp() {
- final PrintWriter pw = getOutputWriter();
- pw.println("Thread network commands:");
- pw.println(" help or -h");
- pw.println(" Print this help text.");
- onHelpNonPrivileged(pw);
- if (Binder.getCallingUid() == Process.ROOT_UID) {
- onHelpPrivileged(pw);
- }
- pw.println();
- }
}
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index b76c1e9..976f93d 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -16,30 +16,40 @@
package com.android.server.thread;
+import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.EADDRINUSE;
+import static android.system.OsConstants.IFF_MULTICAST;
+import static android.system.OsConstants.IFF_NOARP;
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_AF_SPEC;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_INET6_ADDR_GEN_MODE;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IN6_ADDR_GEN_MODE_NONE;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
import android.annotation.Nullable;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.RouteInfo;
-import android.net.util.SocketUtils;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
-import android.system.OsConstants;
import android.util.Log;
+import com.android.net.module.util.HexDump;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.net.module.util.netlink.NetlinkUtils;
-import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
+import com.android.net.module.util.netlink.StructIfinfoMsg;
+import com.android.net.module.util.netlink.StructNlAttr;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
import com.android.server.thread.openthread.Ipv6AddressInfo;
import com.android.server.thread.openthread.OnMeshPrefixConfig;
-import java.io.FileDescriptor;
import java.io.IOException;
-import java.io.InterruptedIOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -47,12 +57,15 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
/** Controller for virtual/tunnel network interfaces. */
public class TunInterfaceController {
private static final String TAG = "TunIfController";
+ private static final boolean DBG = false;
private static final long INFINITE_LIFETIME = 0xffffffffL;
static final int MTU = 1280;
@@ -62,14 +75,13 @@
private final String mIfName;
private final LinkProperties mLinkProperties = new LinkProperties();
- private ParcelFileDescriptor mParcelTunFd;
- private FileDescriptor mNetlinkSocket;
- private static int sNetlinkSeqNo = 0;
private final MulticastSocket mMulticastSocket; // For join group and leave group
- private NetworkInterface mNetworkInterface;
private final List<InetAddress> mMulticastAddresses = new ArrayList<>();
private final List<RouteInfo> mNetDataPrefixes = new ArrayList<>();
+ private ParcelFileDescriptor mParcelTunFd;
+ private NetworkInterface mNetworkInterface;
+
/** Creates a new {@link TunInterfaceController} instance for given interface. */
public TunInterfaceController(String interfaceName) {
mIfName = interfaceName;
@@ -91,26 +103,21 @@
public void createTunInterface() throws IOException {
mParcelTunFd = ParcelFileDescriptor.adoptFd(nativeCreateTunInterface(mIfName, MTU));
try {
- mNetlinkSocket = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_ROUTE);
- } catch (ErrnoException e) {
- throw new IOException("Failed to create netlink socket", e);
- }
- try {
mNetworkInterface = NetworkInterface.getByName(mIfName);
} catch (SocketException e) {
throw new IOException("Failed to get NetworkInterface", e);
}
+
+ setAddrGenModeToNone();
}
public void destroyTunInterface() {
try {
mParcelTunFd.close();
- SocketUtils.closeSocket(mNetlinkSocket);
} catch (IOException e) {
// Should never fail
}
mParcelTunFd = null;
- mNetlinkSocket = null;
mNetworkInterface = null;
}
@@ -142,14 +149,14 @@
public void addAddress(LinkAddress address) {
Log.d(TAG, "Adding address " + address + " with flags: " + address.getFlags());
- long validLifetimeSeconds;
long preferredLifetimeSeconds;
+ long validLifetimeSeconds;
if (address.getDeprecationTime() == LinkAddress.LIFETIME_PERMANENT
|| address.getDeprecationTime() == LinkAddress.LIFETIME_UNKNOWN) {
- validLifetimeSeconds = INFINITE_LIFETIME;
+ preferredLifetimeSeconds = INFINITE_LIFETIME;
} else {
- validLifetimeSeconds =
+ preferredLifetimeSeconds =
Math.max(
(address.getDeprecationTime() - SystemClock.elapsedRealtime()) / 1000L,
0L);
@@ -157,28 +164,23 @@
if (address.getExpirationTime() == LinkAddress.LIFETIME_PERMANENT
|| address.getExpirationTime() == LinkAddress.LIFETIME_UNKNOWN) {
- preferredLifetimeSeconds = INFINITE_LIFETIME;
+ validLifetimeSeconds = INFINITE_LIFETIME;
} else {
- preferredLifetimeSeconds =
+ validLifetimeSeconds =
Math.max(
(address.getExpirationTime() - SystemClock.elapsedRealtime()) / 1000L,
0L);
}
- byte[] message =
- RtNetlinkAddressMessage.newRtmNewAddressMessage(
- sNetlinkSeqNo++,
- address.getAddress(),
- (short) address.getPrefixLength(),
- address.getFlags(),
- (byte) address.getScope(),
- Os.if_nametoindex(mIfName),
- validLifetimeSeconds,
- preferredLifetimeSeconds);
- try {
- Os.write(mNetlinkSocket, message, 0, message.length);
- } catch (ErrnoException | InterruptedIOException e) {
- Log.e(TAG, "Failed to add address " + address, e);
+ if (!NetlinkUtils.sendRtmNewAddressRequest(
+ Os.if_nametoindex(mIfName),
+ address.getAddress(),
+ (short) address.getPrefixLength(),
+ address.getFlags(),
+ (byte) address.getScope(),
+ preferredLifetimeSeconds,
+ validLifetimeSeconds)) {
+ Log.w(TAG, "Failed to add address " + address.getAddress().getHostAddress());
return;
}
mLinkProperties.addLinkAddress(address);
@@ -188,22 +190,17 @@
/** Removes an address from the interface. */
public void removeAddress(LinkAddress address) {
Log.d(TAG, "Removing address " + address);
- byte[] message =
- RtNetlinkAddressMessage.newRtmDelAddressMessage(
- sNetlinkSeqNo++,
- address.getAddress(),
- (short) address.getPrefixLength(),
- Os.if_nametoindex(mIfName));
// Intentionally update the mLinkProperties before send netlink message because the
// address is already removed from ot-daemon and apps can't reach to the address even
// when the netlink request below fails
mLinkProperties.removeLinkAddress(address);
mLinkProperties.removeRoute(getRouteForAddress(address));
- try {
- Os.write(mNetlinkSocket, message, 0, message.length);
- } catch (ErrnoException | InterruptedIOException e) {
- Log.e(TAG, "Failed to remove address " + address, e);
+ if (!NetlinkUtils.sendRtmDelAddressRequest(
+ Os.if_nametoindex(mIfName),
+ (Inet6Address) address.getAddress(),
+ (short) address.getPrefixLength())) {
+ Log.w(TAG, "Failed to remove address " + address.getAddress().getHostAddress());
}
}
@@ -366,4 +363,66 @@
Log.e(TAG, "failed to leave group " + address.getHostAddress(), e);
}
}
+
+ /**
+ * Sets the address generation mode to {@code IN6_ADDR_GEN_MODE_NONE}.
+ *
+ * <p>So that the "thread-wpan" interface has only one IPv6 link local address which is
+ * generated by OpenThread.
+ */
+ private void setAddrGenModeToNone() {
+ StructNlMsgHdr header = new StructNlMsgHdr();
+ header.nlmsg_type = RTM_NEWLINK;
+ header.nlmsg_pid = 0;
+ header.nlmsg_seq = 0;
+ header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+ StructIfinfoMsg ifInfo =
+ new StructIfinfoMsg(
+ (short) 0 /* family */,
+ 0 /* type */,
+ Os.if_nametoindex(mIfName),
+ (IFF_MULTICAST | IFF_NOARP) /* flags */,
+ 0xffffffff /* change */);
+
+ // Nested attributes
+ // IFLA_AF_SPEC
+ // AF_INET6
+ // IFLA_INET6_ADDR_GEN_MODE
+ StructNlAttr addrGenMode =
+ new StructNlAttr(IFLA_INET6_ADDR_GEN_MODE, (byte) IN6_ADDR_GEN_MODE_NONE);
+ StructNlAttr afInet6 = new StructNlAttr((short) AF_INET6, addrGenMode);
+ StructNlAttr afSpec = new StructNlAttr(IFLA_AF_SPEC, afInet6);
+
+ final int msgLength =
+ StructNlMsgHdr.STRUCT_SIZE
+ + StructIfinfoMsg.STRUCT_SIZE
+ + afSpec.getAlignedLength();
+ byte[] msg = new byte[msgLength];
+ ByteBuffer buf = ByteBuffer.wrap(msg);
+ buf.order(ByteOrder.nativeOrder());
+
+ header.nlmsg_len = msgLength;
+ header.pack(buf);
+ ifInfo.pack(buf);
+ afSpec.pack(buf);
+
+ if (buf.position() != msgLength) {
+ throw new AssertionError(
+ String.format(
+ "Unexpected netlink message size (actual = %d, expected = %d)",
+ buf.position(), msgLength));
+ }
+
+ if (DBG) {
+ Log.d(TAG, "ADDR_GEN_MODE message is:");
+ Log.d(TAG, HexDump.dumpHexString(msg));
+ }
+
+ try {
+ NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to set ADDR_GEN_MODE to NONE", e);
+ }
+ }
}
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 91ca23e..61b6eac 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -176,7 +176,7 @@
// TODO (b/323300829): add test for removing an OT address
@Test
- public void tunInterface_joinedNetwork_otAddressesAddedToTunInterface() throws Exception {
+ public void tunInterface_joinedNetwork_otAndTunAddressesMatch() throws Exception {
mController.joinAndWait(DEFAULT_DATASET);
List<Inet6Address> otAddresses = mOtCtl.getAddresses();
@@ -185,9 +185,12 @@
// that we can write assertThat() in the Predicate
waitFor(
() -> {
- String ifconfig = runShellCommand("ifconfig thread-wpan");
- return otAddresses.stream()
- .allMatch(addr -> ifconfig.contains(addr.getHostAddress()));
+ List<Inet6Address> tunAddresses =
+ getIpv6LinkAddresses("thread-wpan").stream()
+ .map(linkAddr -> (Inet6Address) linkAddr.getAddress())
+ .toList();
+ return otAddresses.containsAll(tunAddresses)
+ && tunAddresses.containsAll(otAddresses);
},
TUN_ADDR_UPDATE_TIMEOUT);
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index 78f5770..ada46c8 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -301,7 +301,7 @@
return false;
}
- public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) throws IOException {
+ public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) {
List<LinkAddress> addresses = new ArrayList<>();
final String cmd = " ip -6 addr show dev " + interfaceName;
final String output = runShellCommandOrThrow(cmd);
diff --git a/thread/tests/multidevices/AndroidTest.xml b/thread/tests/multidevices/AndroidTest.xml
index a2ea9aa..8b2bed3 100644
--- a/thread/tests/multidevices/AndroidTest.xml
+++ b/thread/tests/multidevices/AndroidTest.xml
@@ -44,7 +44,7 @@
<test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
<!-- The mobly-par-file-name should match the module name -->
- <option name="mobly-par-file-name" value="ThreadMultiDeviceTestCases" />
+ <option name="mobly-par-file-name" value="ThreadNetworkMultiDeviceTests" />
<!-- Timeout limit in milliseconds for all test cases of the python binary -->
<option name="mobly-test-timeout" value="180000" />
</test>
diff --git a/thread/tests/unit/Android.bp b/thread/tests/unit/Android.bp
index 3365cd0..9404d1b 100644
--- a/thread/tests/unit/Android.bp
+++ b/thread/tests/unit/Android.bp
@@ -33,6 +33,7 @@
"mts-tethering",
],
static_libs: [
+ "androidx.test.rules",
"frameworks-base-testutils",
"framework-connectivity-pre-jarjar",
"framework-connectivity-t-pre-jarjar",
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 52a9dd9..8f60783 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -64,16 +64,19 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserManager;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.util.AtomicFile;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.connectivity.resources.R;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.MeshcopTxtAttributes;
import com.android.server.thread.openthread.testing.FakeOtDaemon;
@@ -89,7 +92,10 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import java.time.Clock;
+import java.time.DateTimeException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -97,6 +103,12 @@
/** Unit tests for {@link ThreadNetworkControllerService}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+// This test doesn't really need to run on the UI thread, but @Before and @Test annotated methods
+// need to run in the same thread because there are code in {@code ThreadNetworkControllerService}
+// checking that all its methods are running in the thread of the handler it's using. This is due
+// to a bug in TestLooper that it executes all tasks on the current thread rather than the thread
+// associated to the backed Looper object.
+@UiThreadTest
public final class ThreadNetworkControllerServiceTest {
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
// Active Timestamp: 1
@@ -528,6 +540,53 @@
}
@Test
+ public void
+ createRandomizedDataset_noNetworkTimeClock_datasetActiveTimestampIsNotAuthoritative()
+ throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock())
+ .thenThrow(new DateTimeException("fake throw"));
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().isAuthoritativeSource()).isFalse();
+ }
+
+ @Test
+ public void createRandomizedDataset_hasNetworkTimeClock_datasetActiveTimestampIsAuthoritative()
+ throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock())
+ .thenReturn(Clock.systemUTC());
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().isAuthoritativeSource()).isTrue();
+ }
+
+ @Test
public void createRandomizedDataset_succeed_activeDatasetCreated() throws Exception {
final IActiveOperationalDatasetReceiver mockReceiver =
mock(IActiveOperationalDatasetReceiver.class);
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
index 9f2d0cb..dfb3129 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
@@ -16,22 +16,29 @@
package com.android.server.thread;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.contains;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.PendingOperationalDataset;
import android.os.Binder;
-import android.os.Process;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -39,6 +46,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -49,19 +57,43 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ThreadNetworkShellCommandTest {
- private static final String TAG = "ThreadNetworkShellCommandTTest";
- @Mock ThreadNetworkControllerService mControllerService;
- @Mock ThreadNetworkCountryCode mCountryCode;
- @Mock PrintWriter mErrorWriter;
- @Mock PrintWriter mOutputWriter;
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
+ // Active Timestamp: 1
+ // Channel: 19
+ // Channel Mask: 0x07FFF800
+ // Ext PAN ID: ACC214689BC40BDF
+ // Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
+ // Network Key: F26B3153760F519A63BAFDDFFC80D2AF
+ // Network Name: OpenThread-d9a0
+ // PAN ID: 0xD9A0
+ // PSKc: A245479C836D551B9CA557F7B9D351B4
+ // Security Policy: 672 onrcb
+ private static final String DEFAULT_ACTIVE_DATASET_TLVS =
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8";
- ThreadNetworkShellCommand mShellCommand;
+ @Mock private ThreadNetworkControllerService mControllerService;
+ @Mock private ThreadNetworkCountryCode mCountryCode;
+ @Mock private PrintWriter mErrorWriter;
+ @Mock private PrintWriter mOutputWriter;
+
+ private Context mContext;
+ private ThreadNetworkShellCommand mShellCommand;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mShellCommand = new ThreadNetworkShellCommand(mControllerService, mCountryCode);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ doNothing()
+ .when(mContext)
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
+
+ mShellCommand = new ThreadNetworkShellCommand(mContext, mControllerService, mCountryCode);
mShellCommand.setPrintWriters(mOutputWriter, mErrorWriter);
}
@@ -71,8 +103,23 @@
}
@Test
- public void getCountryCode_executeInUnrootedShell_allowed() {
- BinderUtil.setUid(Process.SHELL_UID);
+ public void getCountryCode_testingPermissionIsChecked() {
+ when(mCountryCode.getCountryCode()).thenReturn("US");
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"get-country-code"});
+
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
+ }
+
+ @Test
+ public void getCountryCode_currentCountryCodePrinted() {
when(mCountryCode.getCountryCode()).thenReturn("US");
mShellCommand.exec(
@@ -86,9 +133,7 @@
}
@Test
- public void forceSetCountryCodeEnabled_executeInUnrootedShell_notAllowed() {
- BinderUtil.setUid(Process.SHELL_UID);
-
+ public void forceSetCountryCodeEnabled_testingPermissionIsChecked() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -96,14 +141,13 @@
new FileDescriptor(),
new String[] {"force-country-code", "enabled", "US"});
- verify(mCountryCode, never()).setOverrideCountryCode(eq("US"));
- verify(mErrorWriter).println(contains("force-country-code"));
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
}
@Test
- public void forceSetCountryCodeEnabled_executeInRootedShell_allowed() {
- BinderUtil.setUid(Process.ROOT_UID);
-
+ public void forceSetCountryCodeEnabled_countryCodeIsOverridden() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -115,24 +159,7 @@
}
@Test
- public void forceSetCountryCodeDisabled_executeInUnrootedShell_notAllowed() {
- BinderUtil.setUid(Process.SHELL_UID);
-
- mShellCommand.exec(
- new Binder(),
- new FileDescriptor(),
- new FileDescriptor(),
- new FileDescriptor(),
- new String[] {"force-country-code", "disabled"});
-
- verify(mCountryCode, never()).setOverrideCountryCode(any());
- verify(mErrorWriter).println(contains("force-country-code"));
- }
-
- @Test
- public void forceSetCountryCodeDisabled_executeInRootedShell_allowed() {
- BinderUtil.setUid(Process.ROOT_UID);
-
+ public void forceSetCountryCodeDisabled_overriddenCountryCodeIsCleared() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -144,9 +171,7 @@
}
@Test
- public void forceStopOtDaemon_executeInUnrootedShell_failedAndServiceApiNotCalled() {
- BinderUtil.setUid(Process.SHELL_UID);
-
+ public void forceStopOtDaemon_testingPermissionIsChecked() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -154,14 +179,13 @@
new FileDescriptor(),
new String[] {"force-stop-ot-daemon", "enabled"});
- verify(mControllerService, never()).forceStopOtDaemonForTest(anyBoolean(), any());
- verify(mErrorWriter, atLeastOnce()).println(contains("force-stop-ot-daemon"));
- verify(mOutputWriter, never()).println();
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
}
@Test
public void forceStopOtDaemon_serviceThrows_failed() {
- BinderUtil.setUid(Process.ROOT_UID);
doThrow(new SecurityException(""))
.when(mControllerService)
.forceStopOtDaemonForTest(eq(true), any());
@@ -179,7 +203,6 @@
@Test
public void forceStopOtDaemon_serviceApiTimeout_failedWithTimeoutError() {
- BinderUtil.setUid(Process.ROOT_UID);
doNothing().when(mControllerService).forceStopOtDaemonForTest(eq(true), any());
mShellCommand.exec(
@@ -193,4 +216,89 @@
verify(mErrorWriter, atLeastOnce()).println(contains("timeout"));
verify(mOutputWriter, never()).println();
}
+
+ @Test
+ public void join_controllerServiceJoinIsCalled() {
+ doNothing().when(mControllerService).join(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"join", DEFAULT_ACTIVE_DATASET_TLVS});
+
+ var activeDataset =
+ ActiveOperationalDataset.fromThreadTlvs(
+ base16().decode(DEFAULT_ACTIVE_DATASET_TLVS));
+ verify(mControllerService, times(1)).join(eq(activeDataset), any());
+ verify(mErrorWriter, never()).println();
+ }
+
+ @Test
+ public void join_invalidDataset_controllerServiceJoinIsNotCalled() {
+ doNothing().when(mControllerService).join(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"join", "000102"});
+
+ verify(mControllerService, never()).join(any(), any());
+ verify(mErrorWriter, times(1)).println(contains("Invalid dataset argument"));
+ }
+
+ @Test
+ public void migrate_controllerServiceMigrateIsCalled() {
+ doNothing().when(mControllerService).scheduleMigration(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"migrate", DEFAULT_ACTIVE_DATASET_TLVS, "300"});
+
+ ArgumentCaptor<PendingOperationalDataset> captor =
+ ArgumentCaptor.forClass(PendingOperationalDataset.class);
+ verify(mControllerService, times(1)).scheduleMigration(captor.capture(), any());
+ assertThat(captor.getValue().getActiveOperationalDataset())
+ .isEqualTo(
+ ActiveOperationalDataset.fromThreadTlvs(
+ base16().decode(DEFAULT_ACTIVE_DATASET_TLVS)));
+ assertThat(captor.getValue().getDelayTimer().toSeconds()).isEqualTo(300);
+ verify(mErrorWriter, never()).println();
+ }
+
+ @Test
+ public void migrate_invalidDataset_controllerServiceMigrateIsNotCalled() {
+ doNothing().when(mControllerService).scheduleMigration(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"migrate", "000102", "300"});
+
+ verify(mControllerService, never()).scheduleMigration(any(), any());
+ verify(mErrorWriter, times(1)).println(contains("Invalid dataset argument"));
+ }
+
+ @Test
+ public void leave_controllerServiceLeaveIsCalled() {
+ doNothing().when(mControllerService).leave(any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"leave"});
+
+ verify(mControllerService, times(1)).leave(any());
+ verify(mErrorWriter, never()).println();
+ }
}
diff --git a/tools/aospify_device.sh b/tools/aospify_device.sh
index f25ac9d..0176093 100755
--- a/tools/aospify_device.sh
+++ b/tools/aospify_device.sh
@@ -3,10 +3,14 @@
# Script to swap core networking modules in a GMS userdebug device to AOSP modules, by remounting
# the system partition and replacing module prebuilts. This is only to be used for local testing,
# and should only be used on userdebug devices that support "adb root" and remounting the system
-# partition using overlayfs.
+# partition using overlayfs. The setup wizard should be cleared before running the script.
#
# Usage: aospify_device.sh [device_serial]
-# Reset by wiping data (adb reboot bootloader && fastboot erase userdata && fastboot reboot).
+#
+# Reset with "adb enable-verity", then wiping data (from Settings, or:
+# "adb reboot bootloader && fastboot erase userdata && fastboot reboot").
+# Some devices output errors like "Overlayfs teardown failed" on "enable-verity" but it still works
+# (/mnt/scratch should be deleted).
#
# This applies to NetworkStack, CaptivePortalLogin, dnsresolver, tethering, cellbroadcast modules,
# which generally need to be preloaded together (core networking modules + cellbroadcast which
@@ -37,6 +41,10 @@
else
rm -f /tmp/decompressed_$aosp_apex_name.apex
$ANDROID_HOST_OUT/bin/deapexer decompress --input $ANDROID_PRODUCT_OUT/system/apex/$aosp_apex_name.capex --output /tmp/decompressed_$aosp_apex_name.apex
+ if ! $ADB_CMD shell ls /system/apex/$original_apex_name.apex 1>/dev/null 2>/dev/null; then
+ # Filename observed on some phones, even though it is not actually compressed
+ original_apex_name=${original_apex_name}_compressed
+ fi
$ADB_CMD shell rm /system/apex/$original_apex_name.apex
$ADB_CMD push /tmp/decompressed_$aosp_apex_name.apex /system/apex/$aosp_apex_name.apex
rm /tmp/decompressed_$aosp_apex_name.apex
@@ -47,7 +55,7 @@
local app_type=$1
local original_apk_name=$2
local aosp_apk_name=$3
- $ADB_CMD shell rm /system/$app_type/$original_apk_name/$original_apk_name.apk
+ $ADB_CMD shell rm /system/$app_type/$original_apk_name/$original_apk_name*.apk
$ADB_CMD push $ANDROID_PRODUCT_OUT/system/$app_type/$aosp_apk_name/$aosp_apk_name.apk /system/$app_type/$original_apk_name/
}
@@ -97,7 +105,7 @@
exit 1
fi
-if ! $ADB_CMD wait-for-device shell pm path com.google.android.networkstack; then
+if ! $ADB_CMD wait-for-device shell pm path com.google.android.networkstack 1>/dev/null 2>/dev/null; then
echo "This device is already not using GMS modules"
exit 1
fi
@@ -122,8 +130,7 @@
$ADB_CMD reboot
echo "Waiting for boot..."
-$ADB_CMD wait-for-device;
-until [[ $($ADB_CMD shell getprop sys.boot_completed) == 1 ]]; do
+until [[ $($ADB_CMD wait-for-device shell getprop sys.boot_completed) == 1 ]]; do
sleep 1;
done
@@ -146,7 +153,12 @@
# Update the networkstack privapp-permissions allowlist
rm -f /tmp/pulled_privapp-permissions.xml
-$ADB_CMD pull /system/etc/permissions/privapp-permissions-google.xml /tmp/pulled_privapp-permissions.xml
+networkstack_permissions=/system/etc/permissions/GoogleNetworkStack_permissions.xml
+if ! $ADB_CMD shell ls $networkstack_permissions 1>/dev/null 2>/dev/null; then
+ networkstack_permissions=/system/etc/permissions/privapp-permissions-google.xml
+fi
+
+$ADB_CMD pull $networkstack_permissions /tmp/pulled_privapp-permissions.xml
# Remove last </permission> line, and the permissions for com.google.android.networkstack
sed -nE '1,/<\/permissions>/p' /tmp/pulled_privapp-permissions.xml \
@@ -156,7 +168,7 @@
>> /tmp/modified_privapp-permissions.xml
echo '</permissions>' >> /tmp/modified_privapp-permissions.xml
-$ADB_CMD push /tmp/modified_privapp-permissions.xml /system/etc/permissions/privapp-permissions-google.xml
+$ADB_CMD push /tmp/modified_privapp-permissions.xml $networkstack_permissions
rm /tmp/pulled_privapp-permissions.xml /tmp/modified_privapp-permissions.xml