Merge "[Thread] add country code overlay to enable/disable setting Thread country code" into main
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 3c91a1b..5f0e5d0 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -24,6 +24,7 @@
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+import static com.android.net.module.util.SdkUtil.isAtLeast25Q2;
import android.content.ContentResolver;
import android.content.Context;
@@ -395,7 +396,7 @@
* use the async state machine.
*/
public void readEnableSyncSM(final Context ctx) {
- USE_SYNC_SM = mDeps.isFeatureNotChickenedOut(ctx, TETHER_ENABLE_SYNC_SM);
+ USE_SYNC_SM = isAtLeast25Q2() || mDeps.isFeatureNotChickenedOut(ctx, TETHER_ENABLE_SYNC_SM);
}
/** Does the dumping.*/
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index f501a50..b92cf69 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -58,6 +59,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.networkstack.apishim.SettingsShimImpl;
import com.android.networkstack.apishim.common.SettingsShim;
+import com.android.networkstack.tethering.util.TetheringPermissionsUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -72,6 +74,7 @@
private TetheringConnector mConnector;
private SettingsShim mSettingsShim;
+ private TetheringPermissionsUtils mTetheringPermissionsUtils;
@Override
public void onCreate() {
@@ -81,6 +84,7 @@
mConnector = new TetheringConnector(makeTethering(deps), TetheringService.this);
mSettingsShim = SettingsShimImpl.newInstance();
+ mTetheringPermissionsUtils = new TetheringPermissionsUtils(deps.getContext());
}
/**
@@ -109,7 +113,11 @@
@Override
public void tether(String iface, String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+ listener)) {
+ return;
+ }
mTethering.legacyTether(iface, listener);
}
@@ -117,7 +125,11 @@
@Override
public void untether(String iface, String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+ listener)) {
+ return;
+ }
mTethering.legacyUntether(iface, listener);
}
@@ -125,32 +137,45 @@
@Override
public void setUsbTethering(boolean enable, String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+ listener)) {
+ return;
+ }
mTethering.setUsbTethering(enable, listener);
}
+ private boolean isRequestAllowedForDeviceOwner(@NonNull TetheringRequest request) {
+ return request.getTetheringType() == TETHERING_WIFI
+ && request.getSoftApConfiguration() != null;
+ }
+
@Override
- public void startTethering(TetheringRequestParcel request, String callerPkg,
+ public void startTethering(TetheringRequestParcel requestParcel, String callerPkg,
String callingAttributionTag, IIntResultListener listener) {
- boolean onlyAllowPrivileged = request.exemptFromEntitlementCheck
- || request.interfaceName != null;
- if (checkAndNotifyCommonError(callerPkg,
- callingAttributionTag,
- onlyAllowPrivileged,
- listener)) {
+ TetheringRequest request = new TetheringRequest(requestParcel);
+ request.setUid(getBinderCallingUid());
+ request.setPackageName(callerPkg);
+ boolean onlyAllowPrivileged = request.isExemptFromEntitlementCheck()
+ || request.getInterfaceName() != null;
+ boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
+ && isRequestAllowedForDeviceOwner(request);
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, onlyAllowPrivileged,
+ isDeviceOwnerAllowed, listener)) {
return;
}
- TetheringRequest external = new TetheringRequest(request);
- external.setUid(getBinderCallingUid());
- external.setPackageName(callerPkg);
- mTethering.startTethering(external, callerPkg, listener);
+ mTethering.startTethering(request, callerPkg, listener);
}
@Override
public void stopTethering(int type, String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+ listener)) {
+ return;
+ }
try {
mTethering.stopTethering(type);
@@ -164,9 +189,16 @@
IIntResultListener listener) {
if (request == null) return;
if (listener == null) return;
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
request.setUid(getBinderCallingUid());
request.setPackageName(callerPkg);
+ boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
+ && isRequestAllowedForDeviceOwner(request);
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, isDeviceOwnerAllowed, listener)) {
+ return;
+ }
+ // Note: Whether tethering is actually stopped or not will depend on whether the request
+ // matches an active one with the same UID (see RequestTracker#findFuzzyMatchedRequest).
mTethering.stopTetheringRequest(request, listener);
}
@@ -187,7 +219,11 @@
+ " internal-only listener");
}
};
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+ listener)) {
+ return;
+ }
mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
}
@@ -223,7 +259,11 @@
@Override
public void stopAllTethering(String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+ listener)) {
+ return;
+ }
try {
mTethering.stopAllTethering();
@@ -234,8 +274,11 @@
@Override
public void isTetheringSupported(String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
-
+ boolean isDeviceOwnerAppAllowed = mTethering.isTetheringWithSoftApConfigEnabled();
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, isDeviceOwnerAppAllowed, listener)) {
+ return;
+ }
try {
listener.onResult(TETHER_ERROR_NO_ERROR);
} catch (RemoteException e) { }
@@ -260,23 +303,17 @@
}
private boolean checkAndNotifyCommonError(final String callerPkg,
- final String callingAttributionTag, final IIntResultListener listener) {
- return checkAndNotifyCommonError(callerPkg, callingAttributionTag,
- false /* onlyAllowPrivileged */, listener);
- }
-
- private boolean checkAndNotifyCommonError(final String callerPkg,
final String callingAttributionTag, final boolean onlyAllowPrivileged,
- final IIntResultListener listener) {
+ final boolean isDeviceOwnerAppAllowed, final IIntResultListener listener) {
try {
- if (!checkPackageNameMatchesUid(getBinderCallingUid(), callerPkg)) {
- Log.e(TAG, "Package name " + callerPkg + " does not match UID "
- + getBinderCallingUid());
+ final int uid = getBinderCallingUid();
+ if (!checkPackageNameMatchesUid(uid, callerPkg)) {
+ Log.e(TAG, "Package name " + callerPkg + " does not match UID " + uid);
listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
return true;
}
- if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
- onlyAllowPrivileged)) {
+ if (!hasTetherChangePermission(uid, callerPkg, callingAttributionTag,
+ onlyAllowPrivileged, isDeviceOwnerAppAllowed)) {
listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
return true;
}
@@ -308,21 +345,25 @@
return mService.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
}
- private boolean hasTetherChangePermission(final String callerPkg,
- final String callingAttributionTag, final boolean onlyAllowPrivileged) {
+ private boolean hasTetherChangePermission(final int uid, final String callerPkg,
+ final String callingAttributionTag, final boolean onlyAllowPrivileged,
+ final boolean isDeviceOwnerAppAllowed) {
if (onlyAllowPrivileged && !hasNetworkStackPermission()
&& !hasNetworkSettingsPermission()) return false;
if (hasTetherPrivilegedPermission()) return true;
+ // Allow DO apps to change tethering even if they don't have TETHER_PRIVILEGED.
+ if (isDeviceOwnerAppAllowed && mService.isDeviceOwner(uid, callerPkg)) {
+ return true;
+ }
+
// After TetheringManager moves to public API, prevent third-party apps from being able
// to change tethering with only WRITE_SETTINGS permission.
if (mTethering.isTetheringWithSoftApConfigEnabled()) return false;
if (mTethering.isTetherProvisioningRequired()) return false;
- int uid = getBinderCallingUid();
-
// If callerPkg's uid is not same as getBinderCallingUid(),
// checkAndNoteWriteSettingsOperation will return false and the operation will be
// denied.
@@ -387,6 +428,14 @@
}
/**
+ * Wrapper for {@link TetheringPermissionsUtils#isDeviceOwner(int, String)}, used for mocks.
+ */
+ @VisibleForTesting
+ boolean isDeviceOwner(final int uid, final String callerPkg) {
+ return mTetheringPermissionsUtils.isDeviceOwner(uid, callerPkg);
+ }
+
+ /**
* An injection method for testing.
*/
@VisibleForTesting
diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java
new file mode 100644
index 0000000..944e861
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025 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.networkstack.tethering.util;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Utils class for checking permissions related to Tethering APIs.
+ */
+public class TetheringPermissionsUtils {
+ private static final String TAG = "TetherPermUtils";
+
+ @NonNull private final Context mContext;
+
+ public TetheringPermissionsUtils(@NonNull Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Checks if the package name is a Device Owner.
+ */
+ public boolean isDeviceOwner(final int uid, @NonNull final String packageName) {
+ Context userContext;
+ try {
+ // There is no safe way to invoke this method since tethering package might not be
+ // installed for a certain user on the OEM devices, refer to b/382628161.
+ userContext = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid),
+ 0 /* flags */);
+ } catch (IllegalStateException e) {
+ // TODO: Add a terrible error metric for this case.
+ Log.e(TAG, "createContextAsUser failed, skipping Device Owner check", e);
+ return false;
+ }
+ DevicePolicyManager devicePolicyManager =
+ retrieveDevicePolicyManagerFromContext(userContext);
+ if (devicePolicyManager == null) return false;
+ return devicePolicyManager.isDeviceOwnerApp(packageName);
+ }
+
+ private DevicePolicyManager retrieveDevicePolicyManagerFromContext(
+ @NonNull final Context context) {
+ DevicePolicyManager devicePolicyManager =
+ context.getSystemService(DevicePolicyManager.class);
+ if (devicePolicyManager == null
+ && context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_DEVICE_ADMIN)) {
+ Log.w(TAG, "Error retrieving DPM service");
+ }
+ return devicePolicyManager;
+ }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
index 7fcc5f1..a8bd221 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
@@ -25,14 +25,18 @@
import android.os.Binder;
import android.os.IBinder;
import android.util.ArrayMap;
+import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.Set;
+
public class MockTetheringService extends TetheringService {
private final Tethering mTethering = mock(Tethering.class);
private final ArrayMap<String, Integer> mMockedPermissions = new ArrayMap<>();
private final ArrayMap<String, Integer> mMockedPackageUids = new ArrayMap<>();
+ private final Set<String> mMockedDeviceOwnerPackages = new ArraySet<>();
private int mMockCallingUid;
@Override
@@ -74,6 +78,11 @@
return mMockCallingUid;
}
+ @Override
+ boolean isDeviceOwner(final int uid, final String callerPkg) {
+ return mMockedDeviceOwnerPackages.contains(callerPkg);
+ }
+
public Tethering getTethering() {
return mTethering;
}
@@ -118,5 +127,19 @@
public void setCallingUid(int uid) {
mMockCallingUid = uid;
}
+
+ /**
+ * Add a mocked carrier privileges package
+ */
+ public void addDeviceOwnerPackage(final String packageName) {
+ mMockedDeviceOwnerPackages.add(packageName);
+ }
+
+ /**
+ * Remove a mocked carrier privileges package
+ */
+ public void removeDeviceOwnerPackage(final String packageName) {
+ mMockedDeviceOwnerPackages.remove(packageName);
+ }
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index 0159573..c4c7c9c 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -63,6 +63,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.SdkUtil;
import com.android.net.module.util.SharedLog;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -779,7 +780,10 @@
setTetherEnableSyncSMFlagEnabled(true);
assertEnableSyncSM(true);
- setTetherEnableSyncSMFlagEnabled(false);
- assertEnableSyncSM(false);
+ // Feature is enabled by default after 25Q2 release.
+ if (!SdkUtil.isAtLeast25Q2()) {
+ setTetherEnableSyncSMFlagEnabled(false);
+ assertEnableSyncSM(false);
+ }
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index b550ada..87163ef 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -21,6 +21,7 @@
import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.Manifest.permission.WRITE_SETTINGS;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
@@ -36,6 +37,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@@ -53,6 +55,7 @@
import android.net.TetheringManager;
import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
+import android.net.wifi.SoftApConfiguration;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
@@ -153,43 +156,58 @@
}
private void runAsNoPermission(final TestTetheringCall test) throws Exception {
- runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
- new String[0]);
+ runTetheringCall(test, true /* isTetheringAllowed */,
+ true /* isTetheringWithSoftApConfigEnabled */, new String[0]);
}
private void runAsTetherPrivileged(final TestTetheringCall test) throws Exception {
- runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
- TETHER_PRIVILEGED);
+ runTetheringCall(test, true /* isTetheringAllowed */,
+ true /* isTetheringWithSoftApConfigEnabled */, TETHER_PRIVILEGED);
}
private void runAsAccessNetworkState(final TestTetheringCall test) throws Exception {
- runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
- ACCESS_NETWORK_STATE);
+ runTetheringCall(test, true /* isTetheringAllowed */,
+ true /* isTetheringWithSoftApConfigEnabled */, ACCESS_NETWORK_STATE);
}
private void runAsWriteSettings(final TestTetheringCall test) throws Exception {
- runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
- WRITE_SETTINGS);
+ runTetheringCall(test, true /* isTetheringAllowed */,
+ true /* isTetheringWithSoftApConfigEnabled */, WRITE_SETTINGS);
}
private void runAsWriteSettingsWhenWriteSettingsAllowed(
final TestTetheringCall test) throws Exception {
- runTetheringCall(test, true /* isTetheringAllowed */, true /* isWriteSettingsAllowed */,
- WRITE_SETTINGS);
+ runTetheringCall(test, true /* isTetheringAllowed */,
+ false /* isTetheringWithSoftApConfigEnabled */, WRITE_SETTINGS);
}
private void runAsTetheringDisallowed(final TestTetheringCall test) throws Exception {
- runTetheringCall(test, false /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
- TETHER_PRIVILEGED);
+ runTetheringCall(test, false /* isTetheringAllowed */,
+ true /* isTetheringWithSoftApConfigEnabled */, TETHER_PRIVILEGED);
}
private void runAsNetworkSettings(final TestTetheringCall test) throws Exception {
- runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
- NETWORK_SETTINGS, TETHER_PRIVILEGED);
+ runTetheringCall(test, true /* isTetheringAllowed */,
+ true /* isTetheringWithSoftApConfigEnabled */, NETWORK_SETTINGS, TETHER_PRIVILEGED);
+ }
+
+ private void runAsDeviceOwner(final TestTetheringCall test) throws Exception {
+ mMockConnector.addDeviceOwnerPackage(TEST_CALLER_PKG);
+ runTetheringCall(test, true /* isTetheringAllowed */,
+ true /* isTetheringWithSoftApConfigEnabled */, new String[0]);
+ mMockConnector.removeDeviceOwnerPackage(TEST_CALLER_PKG);
+ }
+
+ private void runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled(final TestTetheringCall test)
+ throws Exception {
+ mMockConnector.addDeviceOwnerPackage(TEST_CALLER_PKG);
+ runTetheringCall(test, true /* isTetheringAllowed */,
+ false /* isTetheringWithSoftApConfigEnabled */, new String[0]);
+ mMockConnector.removeDeviceOwnerPackage(TEST_CALLER_PKG);
}
private void runTetheringCall(final TestTetheringCall test, boolean isTetheringAllowed,
- boolean isWriteSettingsAllowed, String... permissions) throws Exception {
+ boolean isTetheringWithSoftApConfigEnabled, String... permissions) throws Exception {
// Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level
if (!CollectionUtils.contains(permissions, ACCESS_NETWORK_STATE)) {
mMockConnector.setPermission(ACCESS_NETWORK_STATE, PERMISSION_DENIED);
@@ -200,7 +218,7 @@
when(mTethering.isTetheringSupported()).thenReturn(true);
when(mTethering.isTetheringAllowed()).thenReturn(isTetheringAllowed);
when(mTethering.isTetheringWithSoftApConfigEnabled())
- .thenReturn(!isWriteSettingsAllowed);
+ .thenReturn(isTetheringWithSoftApConfigEnabled);
test.runTetheringCall(new TestTetheringResult());
} finally {
mUiAutomation.dropShellPermissionIdentity();
@@ -226,7 +244,7 @@
runAsNoPermission((result) -> {
mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@@ -239,14 +257,14 @@
runAsWriteSettings((result) -> {
mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runTether(result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@@ -274,7 +292,7 @@
runAsNoPermission((result) -> {
mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@@ -287,14 +305,14 @@
runAsWriteSettings((result) -> {
mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runUnTether(result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@@ -328,7 +346,7 @@
runAsNoPermission((result) -> {
mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@@ -341,14 +359,14 @@
runAsWriteSettings((result) -> {
mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runSetUsbTethering(result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@@ -367,6 +385,7 @@
final TetheringRequestParcel request) throws Exception {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
verify(mTethering).startTethering(
@@ -381,14 +400,61 @@
runAsNoPermission((result) -> {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
+ // Not a Wifi request - Fail
+ runAsDeviceOwner((result) -> {
+ final TetheringRequestParcel notWifi = new TetheringRequestParcel();
+ notWifi.tetheringType = TETHERING_USB;
+ mTetheringConnector.startTethering(notWifi, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
+ result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ verifyNoMoreInteractionsForTethering();
+ });
+
+ // Request has no SoftApConfiguration - Fail
+ runAsDeviceOwner((result) -> {
+ final TetheringRequestParcel noConfig = new TetheringRequestParcel();
+ noConfig.tetheringType = TETHERING_WIFI;
+ mTetheringConnector.startTethering(noConfig, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
+ result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ verifyNoMoreInteractionsForTethering();
+ });
+
+ // Wifi request with SoftApConfiguration - Succeed
+ runAsDeviceOwner((result) -> {
+ final TetheringRequestParcel withConfig = new TetheringRequestParcel();
+ withConfig.tetheringType = TETHERING_WIFI;
+ withConfig.softApConfig = new SoftApConfiguration.Builder().build();
+ mTetheringConnector.startTethering(withConfig, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering).isTetheringSupported();
+ verify(mTethering).isTetheringAllowed();
+ verify(mTethering).startTethering(any(), any(), any());
+ result.assertResult(-1); // No result
+ verifyNoMoreInteractionsForTethering();
+ });
+
+ runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> {
+ mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
+ result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ verify(mTethering).isTetherProvisioningRequired();
+ verifyNoMoreInteractionsForTethering();
+ });
+
runAsTetherPrivileged((result) -> {
mTetheringConnector.startTethering(request, TEST_WRONG_PACKAGE,
TEST_ATTRIBUTION_TAG, result);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering, never()).startTethering(
eq(new TetheringRequest(request)), eq(TEST_WRONG_PACKAGE), eq(result));
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
@@ -403,14 +469,14 @@
runAsWriteSettings((result) -> {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runStartTethering(result, request);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@@ -418,6 +484,7 @@
runAsTetheringDisallowed((result) -> {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
result.assertResult(TETHER_ERROR_UNSUPPORTED);
@@ -447,6 +514,7 @@
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verifyNoMoreInteractionsForTethering();
});
}
@@ -459,6 +527,7 @@
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verifyNoMoreInteractionsForTethering();
}
@@ -495,7 +564,7 @@
runAsNoPermission((result) -> {
mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@@ -508,14 +577,14 @@
runAsWriteSettings((result) -> {
mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runStopTethering(result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@@ -530,6 +599,87 @@
});
}
+ private void verifyHasPermissionForStopTetheringRequest(TetheringRequest request,
+ final TestTetheringResult result) throws Exception {
+ mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
+ TEST_ATTRIBUTION_TAG, result);
+ verify(mTethering).stopTetheringRequest(any(), any());
+ verify(mTethering).isTetheringSupported();
+ verify(mTethering).isTetheringAllowed();
+ reset(mTethering);
+ }
+
+ private void verifyDoesNotHavePermissionForStopTetheringRequest(TetheringRequest request,
+ final TestTetheringResult result) throws Exception {
+ mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
+ TEST_ATTRIBUTION_TAG, result);
+ verify(mTethering, never()).stopTetheringRequest(any(), any());
+ result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ reset(mTethering);
+ }
+
+ private void verifyStopTetheringRequestWithTetheringDisallowed(TetheringRequest request,
+ final TestTetheringResult result) throws Exception {
+ mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
+ TEST_ATTRIBUTION_TAG, result);
+ verify(mTethering, never()).stopTetheringRequest(any(), any());
+ result.assertResult(TETHER_ERROR_UNSUPPORTED);
+ reset(mTethering);
+ }
+
+ @Test
+ public void testStopTetheringRequest() throws Exception {
+ TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+
+ runAsNoPermission((result) -> {
+ verifyDoesNotHavePermissionForStopTetheringRequest(request, result);
+ });
+
+ runAsTetherPrivileged((result) -> {
+ verifyHasPermissionForStopTetheringRequest(request, result);
+ });
+
+ runAsWriteSettings((result) -> {
+ verifyDoesNotHavePermissionForStopTetheringRequest(request, result);
+ });
+
+ runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
+ verifyHasPermissionForStopTetheringRequest(request, result);
+ });
+
+ runAsTetheringDisallowed((result) -> {
+ verifyStopTetheringRequestWithTetheringDisallowed(request, result);
+ });
+
+ runAsNetworkSettings((result) -> {
+ verifyHasPermissionForStopTetheringRequest(request, result);
+ });
+
+ // Not wifi -> fail
+ runAsDeviceOwner((result) -> {
+ TetheringRequest notWifi = new TetheringRequest.Builder(TETHERING_USB).build();
+ verifyDoesNotHavePermissionForStopTetheringRequest(notWifi, result);
+ });
+
+ // No config -> fail
+ runAsDeviceOwner((result) -> {
+ TetheringRequest noConfig = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ verifyDoesNotHavePermissionForStopTetheringRequest(noConfig, result);
+ });
+
+ // With config -> success
+ TetheringRequest withConfig = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(new SoftApConfiguration.Builder().build())
+ .build();
+ runAsDeviceOwner((result) -> {
+ verifyHasPermissionForStopTetheringRequest(withConfig, result);
+ });
+
+ runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> {
+ verifyDoesNotHavePermissionForStopTetheringRequest(withConfig, result);
+ });
+ }
+
private void runRequestLatestTetheringEntitlementResult() throws Exception {
final MyResultReceiver result = new MyResultReceiver(null);
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
@@ -547,7 +697,7 @@
runAsNoPermission((none) -> {
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@@ -567,14 +717,14 @@
runAsWriteSettings((none) -> {
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((none) -> {
runRequestLatestTetheringEntitlementResult();
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@@ -668,7 +818,7 @@
public void testStopAllTethering() throws Exception {
runAsNoPermission((result) -> {
mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@@ -680,14 +830,14 @@
runAsWriteSettings((result) -> {
mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runStopAllTethering(result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@@ -703,6 +853,7 @@
private void runIsTetheringSupported(final TestTetheringResult result) throws Exception {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -713,7 +864,7 @@
runAsNoPermission((result) -> {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@@ -726,14 +877,13 @@
runAsWriteSettings((result) -> {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runIsTetheringSupported(result);
- verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@@ -741,6 +891,7 @@
runAsTetheringDisallowed((result) -> {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
+ verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
result.assertResult(TETHER_ERROR_UNSUPPORTED);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java
new file mode 100644
index 0000000..57c3eca
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.networkstack.tethering.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TetheringPermissionsUtilsTest {
+ private static final int TEST_UID = 12345;
+ private static final String TEST_PACKAGE = "test.package";
+
+ @Mock Context mContext;
+ @Mock Context mUserContext;
+ @Mock DevicePolicyManager mDevicePolicyManager;
+ @Mock TelephonyManager mTelephonyManager;
+
+ TetheringPermissionsUtils mTetheringPermissionsUtils;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.createContextAsUser(UserHandle.getUserHandleForUid(TEST_UID), 0))
+ .thenReturn(mUserContext);
+ when(mUserContext.getSystemService(DevicePolicyManager.class))
+ .thenReturn(mDevicePolicyManager);
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+ mTetheringPermissionsUtils = new TetheringPermissionsUtils(mContext);
+ }
+
+ @Test
+ public void testIsDeviceOwner() {
+ when(mDevicePolicyManager.isDeviceOwnerApp(TEST_PACKAGE)).thenReturn(false);
+ assertThat(mTetheringPermissionsUtils.isDeviceOwner(TEST_UID, TEST_PACKAGE)).isFalse();
+
+ when(mDevicePolicyManager.isDeviceOwnerApp(TEST_PACKAGE)).thenReturn(true);
+ assertThat(mTetheringPermissionsUtils.isDeviceOwner(TEST_UID, TEST_PACKAGE)).isTrue();
+ }
+}
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index b9ef766..f11b9a3 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "NetBpfLoad"
#include <arpa/inet.h>
+#include <bpf/btf.h>
#include <bpf/libbpf.h>
#include <dirent.h>
#include <elf.h>
@@ -51,6 +52,7 @@
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
+#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
@@ -649,10 +651,185 @@
return false;
}
+static int setBtfDatasecSize(ifstream &elfFile, struct btf *btf,
+ struct btf_type *bt) {
+ const char *name = btf__name_by_offset(btf, bt->name_off);
+ if (!name) {
+ ALOGE("Couldn't resolve section name, errno: %d", errno);
+ return -errno;
+ }
+
+ vector<char> data;
+ int ret = readSectionByName(name, elfFile, data);
+ if (ret) {
+ ALOGE("Couldn't read section %s, ret: %d", name, ret);
+ return ret;
+ }
+ bt->size = data.size();
+ return 0;
+}
+
+static int getSymOffsetByName(ifstream &elfFile, const char *name, int *off) {
+ vector<Elf64_Sym> symtab;
+ int ret = readSymTab(elfFile, 1 /* sort */, symtab);
+ if (ret) return ret;
+ for (int i = 0; i < (int)symtab.size(); i++) {
+ string s;
+ ret = getSymName(elfFile, symtab[i].st_name, s);
+ if (ret) continue;
+ if (!strcmp(s.c_str(), name)) {
+ *off = symtab[i].st_value;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int setBtfVarOffset(ifstream &elfFile, struct btf *btf,
+ struct btf_type *datasecBt) {
+ int i, vars = btf_vlen(datasecBt);
+ struct btf_var_secinfo *vsi;
+ const char *datasecName = btf__name_by_offset(btf, datasecBt->name_off);
+ if (!datasecName) {
+ ALOGE("Couldn't resolve section name, errno: %d", errno);
+ return -errno;
+ }
+
+ for (i = 0, vsi = btf_var_secinfos(datasecBt); i < vars; i++, vsi++) {
+ const struct btf_type *varBt = btf__type_by_id(btf, vsi->type);
+ if (!varBt || !btf_is_var(varBt)) {
+ ALOGE("Found non VAR kind btf_type, section: %s id: %d", datasecName,
+ vsi->type);
+ return -1;
+ }
+
+ const struct btf_var *var = btf_var(varBt);
+ if (var->linkage == BTF_VAR_STATIC) continue;
+
+ const char *varName = btf__name_by_offset(btf, varBt->name_off);
+ if (!varName) {
+ ALOGE("Failed to resolve var name, section: %s", datasecName);
+ return -1;
+ }
+
+ int off;
+ int ret = getSymOffsetByName(elfFile, varName, &off);
+ if (ret) {
+ ALOGE("No offset found in symbol table, section: %s, var: %s, ret: %d",
+ datasecName, varName, ret);
+ return ret;
+ }
+ vsi->offset = off;
+ }
+ return 0;
+}
+
+static int loadBtf(ifstream &elfFile, struct btf *btf) {
+ int ret;
+ for (unsigned int i = 1; i < btf__type_cnt(btf); ++i) {
+ struct btf_type *bt = (struct btf_type *)btf__type_by_id(btf, i);
+ if (!btf_is_datasec(bt)) continue;
+ ret = setBtfDatasecSize(elfFile, btf, bt);
+ if (ret) return ret;
+ ret = setBtfVarOffset(elfFile, btf, bt);
+ if (ret) return ret;
+ }
+
+ ret = btf__load_into_kernel(btf);
+ if (ret) {
+ if (errno != EINVAL) {
+ ALOGE("btf__load_into_kernel failed, errno: %d", errno);
+ return ret;
+ };
+ // For BTF_KIND_FUNC, newer kernels can read the BTF_INFO_VLEN bits of
+ // struct btf_type to distinguish static vs. global vs. extern
+ // functions, but older kernels enforce that only the BTF_INFO_KIND bits
+ // can be set. Retry with non-BTF_INFO_KIND bits zeroed out to handle
+ // this case.
+ for (unsigned int i = 1; i < btf__type_cnt(btf); ++i) {
+ struct btf_type *bt = (struct btf_type *)btf__type_by_id(btf, i);
+ if (btf_is_func(bt)) {
+ bt->info = (BTF_INFO_KIND(bt->info)) << 24;
+ }
+ }
+ ret = btf__load_into_kernel(btf);
+ if (ret) {
+ ALOGE("btf__load_into_kernel retry failed, errno: %d", errno);
+ return ret;
+ };
+ }
+ return 0;
+}
+
+int getKeyValueTids(const struct btf *btf, const char *mapName,
+ uint32_t expectedKeySize, uint32_t expectedValueSize,
+ uint32_t *keyTypeId, uint32_t *valueTypeId) {
+ const struct btf_type *kvBt;
+ const struct btf_member *key, *value;
+ const size_t max_name = 256;
+ char kvTypeName[max_name];
+ int64_t keySize, valueSize;
+ uint32_t kvId;
+
+ if (snprintf(kvTypeName, max_name, "____btf_map_%s", mapName) == max_name) {
+ ALOGE("____btf_map_%s is too long", mapName);
+ return -1;
+ }
+
+ kvId = btf__find_by_name(btf, kvTypeName);
+ if (kvId < 0) {
+ ALOGE("section not found, map: %s typeName: %s", mapName, kvTypeName);
+ return -1;
+ }
+
+ kvBt = btf__type_by_id(btf, kvId);
+ if (!kvBt) {
+ ALOGE("Couldn't find BTF type, map: %s id: %u", mapName, kvId);
+ return -1;
+ }
+
+ if (!btf_is_struct(kvBt) || btf_vlen(kvBt) < 2) {
+ ALOGE("Non Struct kind or invalid vlen, map: %s id: %u", mapName, kvId);
+ return -1;
+ }
+
+ key = btf_members(kvBt);
+ value = key + 1;
+
+ keySize = btf__resolve_size(btf, key->type);
+ if (keySize < 0) {
+ ALOGE("Couldn't get key size, map: %s errno: %d", mapName, errno);
+ return -1;
+ }
+
+ valueSize = btf__resolve_size(btf, value->type);
+ if (valueSize < 0) {
+ ALOGE("Couldn't get value size, map: %s errno: %d", mapName, errno);
+ return -1;
+ }
+
+ if (expectedKeySize != keySize || expectedValueSize != valueSize) {
+ ALOGE("Key value size mismatch, map: %s key size: %d expected key size: "
+ "%d value size: %d expected value size: %d",
+ mapName, (uint32_t)keySize, expectedKeySize, (uint32_t)valueSize,
+ expectedValueSize);
+ return -1;
+ }
+
+ *keyTypeId = key->type;
+ *valueTypeId = value->type;
+
+ return 0;
+}
+
+static bool isBtfSupported(enum bpf_map_type type) {
+ return type != BPF_MAP_TYPE_DEVMAP_HASH && type != BPF_MAP_TYPE_RINGBUF;
+}
+
static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds,
const char* prefix, const unsigned int bpfloader_ver) {
int ret;
- vector<char> mdData;
+ vector<char> mdData, btfData;
vector<struct bpf_map_def> md;
vector<string> mapNames;
string objName = pathToObjName(string(elfPath));
@@ -679,6 +856,21 @@
ret = getSectionSymNames(elfFile, "maps", mapNames);
if (ret) return ret;
+ ret = readSectionByName(".BTF", elfFile, btfData);
+ if (ret) {
+ ALOGE("Failed to read .BTF section, ret:%d", ret);
+ return ret;
+ }
+ struct btf *btf = btf__new(btfData.data(), btfData.size());
+ if (btf == NULL) {
+ ALOGE("btf__new failed, errno: %d", errno);
+ return -errno;
+ }
+ auto scopeGuard = base::make_scope_guard([btf] { btf__free(btf); });
+
+ ret = loadBtf(elfFile, btf);
+ if (ret) return ret;
+
unsigned kvers = kernelVersion();
for (int i = 0; i < (int)mapNames.size(); i++) {
@@ -804,12 +996,26 @@
};
if (isAtLeastKernelVersion(4, 15, 0))
strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
+
+ bool haveBtf = isBtfSupported(type);
+ if (haveBtf) {
+ uint32_t kTid, vTid;
+ ret = getKeyValueTids(btf, mapNames[i].c_str(), md[i].key_size,
+ md[i].value_size, &kTid, &vTid);
+ if (ret) return ret;
+ req.btf_fd = btf__fd(btf);
+ req.btf_key_type_id = kTid;
+ req.btf_value_type_id = vTid;
+ }
+
fd.reset(bpf(BPF_MAP_CREATE, req));
saved_errno = errno;
if (fd.ok()) {
- ALOGD("bpf_create_map[%s] -> %d", mapNames[i].c_str(), fd.get());
+ ALOGD("bpf_create_map[%s] btf:%d -> %d",
+ mapNames[i].c_str(), haveBtf, fd.get());
} else {
- ALOGE("bpf_create_map[%s] -> %d errno:%d", mapNames[i].c_str(), fd.get(), saved_errno);
+ ALOGE("bpf_create_map[%s] btf:%d -> %d errno:%d",
+ mapNames[i].c_str(), haveBtf, fd.get(), saved_errno);
}
}
@@ -1414,6 +1620,29 @@
return wear;
}
+static int libbpfPrint(enum libbpf_print_level lvl, const char *const formatStr,
+ va_list argList) {
+ int32_t prio;
+ switch (lvl) {
+ case LIBBPF_WARN:
+ prio = ANDROID_LOG_WARN;
+ break;
+ case LIBBPF_INFO:
+ prio = ANDROID_LOG_INFO;
+ break;
+ case LIBBPF_DEBUG:
+ prio = ANDROID_LOG_DEBUG;
+ break;
+ }
+ char *s = strdup(formatStr ?: "(no format string)");
+ int len = strlen(s);
+ if (len && s[len - 1] == '\n')
+ s[len - 1] = 0;
+ LOG_PRI_VA(prio, LOG_TAG, s, argList);
+ free(s);
+ return 0;
+}
+
static int doLoad(char** argv, char * const envp[]) {
if (!isAtLeastS) {
ALOGE("Impossible - not reachable on Android <S.");
@@ -1421,6 +1650,7 @@
// for any possible busted 'optimized' start everything vendor init hacks on R
return 0;
}
+ libbpf_set_print(libbpfPrint);
const bool runningAsRoot = !getuid(); // true iff U QPR3 or V+
diff --git a/bpf/progs/netd.c b/bpf/progs/netd.c
index 08635b3..b146e45 100644
--- a/bpf/progs/netd.c
+++ b/bpf/progs/netd.c
@@ -597,7 +597,7 @@
// V | | | x | x | x | x | x | x | | (netbpfload)
// U | | x | x | x | x | x | x | | |
// T | x | x | x | x | x | x | | | | (magic netbpfload)
-// S | x | x | x | x | x | | | | | (platform loads offload)
+// S | x | x | x | x | x | | | | | (dns netbpfload for offload)
// R | x | x | x | x | | | | | | (no mainline ebpf)
//
// Not relevant for eBPF, but R can also run on 4.4
diff --git a/clatd/clatd.c b/clatd/clatd.c
index bac8b1d..9b1d987 100644
--- a/clatd/clatd.c
+++ b/clatd/clatd.c
@@ -49,7 +49,8 @@
struct clat_config Global_Clatd_Config;
-volatile sig_atomic_t running = 1;
+volatile sig_atomic_t sigterm = 0;
+bool running = true;
// reads IPv6 packet from AF_PACKET socket, translates to IPv4, writes to tun
void process_packet_6_to_4(struct tun_data *tunnel) {
@@ -78,10 +79,11 @@
if (errno != EAGAIN) {
logmsg(ANDROID_LOG_WARN, "%s: read error: %s", __func__, strerror(errno));
}
+ if (errno == ENETDOWN) running = false;
return;
} else if (readlen == 0) {
logmsg(ANDROID_LOG_WARN, "%s: packet socket removed?", __func__);
- running = 0;
+ running = false;
return;
} else if (readlen >= sizeof(buf)) {
logmsg(ANDROID_LOG_WARN, "%s: read truncation - ignoring pkt", __func__);
@@ -161,10 +163,11 @@
if (errno != EAGAIN) {
logmsg(ANDROID_LOG_WARN, "%s: read error: %s", __func__, strerror(errno));
}
+ if (errno == ENETDOWN) running = false; // not sure if this can happen
return;
} else if (readlen == 0) {
logmsg(ANDROID_LOG_WARN, "%s: tun interface removed", __func__);
- running = 0;
+ running = false;
return;
} else if (readlen >= sizeof(buf)) {
logmsg(ANDROID_LOG_WARN, "%s: read truncation - ignoring pkt", __func__);
@@ -273,23 +276,12 @@
* tunnel - tun device data
*/
void event_loop(struct tun_data *tunnel) {
- // Apparently some network gear will refuse to perform NS for IPs that aren't DAD'ed,
- // this would then result in an ipv6-only network with working native ipv6, working
- // IPv4 via DNS64, but non-functioning IPv4 via CLAT (ie. IPv4 literals + IPv4 only apps).
- // The kernel itself doesn't do DAD for anycast ips (but does handle IPV6 MLD and handle ND).
- // So we'll spoof dad here, and yeah, we really should check for a response and in
- // case of failure pick a different IP. Seeing as 48-bits of the IP are utterly random
- // (with the other 16 chosen to guarantee checksum neutrality) this seems like a remote
- // concern...
- // TODO: actually perform true DAD
- send_dad(tunnel->write_fd6, &Global_Clatd_Config.ipv6_local_subnet);
-
struct pollfd wait_fd[] = {
{ tunnel->read_fd6, POLLIN, 0 },
{ tunnel->fd4, POLLIN, 0 },
};
- while (running) {
+ while (running && !sigterm) {
if (poll(wait_fd, ARRAY_SIZE(wait_fd), -1) == -1) {
if (errno != EINTR) {
logmsg(ANDROID_LOG_WARN, "event_loop/poll returned an error: %s", strerror(errno));
diff --git a/clatd/clatd.h b/clatd/clatd.h
index e170c58..11e9687 100644
--- a/clatd/clatd.h
+++ b/clatd/clatd.h
@@ -48,11 +48,13 @@
// plus some extra just-in-case headroom, because it doesn't hurt.
#define MAXDUMPLEN (64 + MAXMTU)
-#define CLATD_VERSION "1.7"
+#define CLATD_VERSION "1.8"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
-extern volatile sig_atomic_t running;
+extern volatile sig_atomic_t sigterm;
+
+void send_dad(int fd, const struct in6_addr* tgt);
void event_loop(struct tun_data *tunnel);
diff --git a/clatd/main.c b/clatd/main.c
index 7aa1671..9e5710e 100644
--- a/clatd/main.c
+++ b/clatd/main.c
@@ -18,12 +18,17 @@
#include <arpa/inet.h>
#include <errno.h>
+#include <linux/audit.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <linux/unistd.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/personality.h>
+#include <sys/prctl.h>
#include <sys/utsname.h>
#include <unistd.h>
@@ -34,10 +39,10 @@
#define DEVICEPREFIX "v4-"
-/* function: stop_loop
+/* function: handle_sigterm
* signal handler: stop the event loop
*/
-static void stop_loop(__attribute__((unused)) int unused) { running = 0; };
+static void handle_sigterm(__attribute__((unused)) int unused) { sigterm = 1; };
/* function: print_help
* in case the user is running this on the command line
@@ -53,6 +58,131 @@
printf("-w [write socket descriptor number]\n");
}
+// Load the architecture identifier (AUDIT_ARCH_* constant)
+#define BPF_SECCOMP_LOAD_AUDIT_ARCH \
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch))
+
+// Load the system call number
+#define BPF_SECCOMP_LOAD_SYSCALL_NR \
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr))
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+// Load the system call argument n, where n is [0..5]
+#define BPF_SECCOMP_LOAD_SYSCALL_ARG_LO32(n) \
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[n]))
+#define BPF_SECCOMP_LOAD_SYSCALL_ARG_HI32(n) \
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[n]) + 4)
+#else
+#error "Not a little endian architecture?"
+#endif
+
+// Allow the system call
+#define BPF_SECCOMP_ALLOW BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
+
+// Allow (but 'audit' log) the system call
+#define BPF_SECCOMP_LOG BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_LOG)
+
+// Reject the system call (kill thread)
+#define BPF_SECCOMP_KILL BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
+
+// Note arguments to BPF_JUMP(opcode, operand, true_offset, false_offset)
+
+// If not equal, jump over count instructions
+#define BPF_JUMP_IF_NOT_EQUAL(v, count) \
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 0, (count))
+
+// If equal, jump over count instructions
+#define BPF_JUMP_IF_EQUAL(v, count) \
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), (count), 0)
+
+// *TWO* instructions: compare and if not equal jump over the allow statement
+#define BPF2_SECCOMP_ALLOW_IF_EQUAL(v) \
+ BPF_JUMP_IF_NOT_EQUAL((v), 1), \
+ BPF_SECCOMP_ALLOW
+
+// *TWO* instructions: compare and if not equal jump over the log statement
+#define BPF2_SECCOMP_LOG_IF_EQUAL(v) \
+ BPF_JUMP_IF_NOT_EQUAL((v), 1), \
+ BPF_SECCOMP_LOG
+
+// *TWO* instructions: compare and if equal jump over the kill statement
+#define BPF2_SECCOMP_KILL_IF_NOT_EQUAL(v) \
+ BPF_JUMP_IF_EQUAL((v), 1), \
+ BPF_SECCOMP_KILL
+
+// Android only supports the following 5 little endian architectures
+#if defined(__aarch64__) && defined(__LP64__)
+ #define MY_AUDIT_ARCH AUDIT_ARCH_AARCH64
+#elif defined(__arm__) && defined(__ILP32__)
+ #define MY_AUDIT_ARCH AUDIT_ARCH_ARM
+#elif defined(__i386__) && defined(__ILP32__)
+ #define MY_AUDIT_ARCH AUDIT_ARCH_I386
+#elif defined(__x86_64__) && defined(__LP64__)
+ #define MY_AUDIT_ARCH AUDIT_ARCH_X86_64
+#elif defined(__riscv) && defined(__LP64__)
+ #define MY_AUDIT_ARCH AUDIT_ARCH_RISCV64
+#else
+ #error "Unknown AUDIT_ARCH_* architecture."
+#endif
+
+void enable_seccomp(void) {
+ static const struct sock_filter filter[] = {
+ BPF_SECCOMP_LOAD_AUDIT_ARCH,
+ BPF2_SECCOMP_KILL_IF_NOT_EQUAL(MY_AUDIT_ARCH),
+
+ BPF_SECCOMP_LOAD_SYSCALL_NR, // aarch64
+
+ // main event loop:
+ // ppoll ( read sendmsg | recvmsg writev )
+ BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_ppoll), // 73
+ BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_read), // 63
+ BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_sendmsg), // 211
+ BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_recvmsg), // 212
+ BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_writev), // 66
+
+ // logging: getuid writev
+ BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_getuid), // 174
+
+ // inbound signal (SIGTERM) processing
+ BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_rt_sigreturn), // 139
+
+ // sleep(n)
+ BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_nanosleep), // 101
+
+ // _exit(0)
+ BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_exit_group), // 94
+
+#if defined(__aarch64__)
+ // Pixels are aarch64 - if we break clatd functionality on them,
+ // we *will* notice on GoogleGuest WiFi network (which is ipv6 only)
+ BPF_SECCOMP_KILL,
+#else
+ // All other architectures: generate audit lines visible in dmesg and logcat
+ BPF_SECCOMP_LOG,
+#endif
+ };
+ static const struct sock_fprog prog = {
+ .len = (unsigned short)ARRAY_SIZE(filter),
+ .filter = (struct sock_filter *)filter,
+ };
+
+ // https://man7.org/linux/man-pages/man2/PR_SET_NO_NEW_PRIVS.2const.html
+ // required to allow non-privileged seccomp filter installation
+ int rv = prctl(PR_SET_NO_NEW_PRIVS, 1L, 0L, 0L, 0L);
+ if (rv) {
+ logmsg(ANDROID_LOG_FATAL, "prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) = %d [%d]", rv, errno);
+ exit(1);
+ }
+
+ // https://man7.org/linux/man-pages/man2/PR_SET_SECCOMP.2const.html
+ // but see also https://man7.org/linux/man-pages/man2/seccomp.2.html
+ rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0L, 0L);
+ if (rv) {
+ logmsg(ANDROID_LOG_FATAL, "prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0) = %d [%d]", rv, errno);
+ exit(1);
+ }
+}
+
/* function: main
* allocate and setup the tun device, then run the event loop
*/
@@ -64,6 +194,48 @@
*write_sock_str = NULL;
unsigned len;
+ // Clatd binary is setuid/gid CLAT, thus when we reach here we have:
+ // $ adb shell ps | grep clat
+ // [pid] [ppid]
+ // clat 7650 1393 10785364 2612 do_sys_poll 0 S clatd-wlan0
+ // $ adb shell cat /proc/7650/status | egrep -i '^(Uid:|Gid:|Groups:)'
+ // [real][effective][saved][filesystem]
+ // [uid] [euid] [suid] [fsuid]
+ // Uid: 1000 1029 1029 1029
+ // [gid] [egid] [sgid] [fsgid]
+ // Gid: 1000 1029 1029 1029
+ // Groups: 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1018 1021 1023 1024 1032 1065 3001 3002 3003 3005 3006 3007 3009 3010 3011 3012
+ // This mismatch between uid & euid appears to cause periodic (every 5 minutes):
+ // objhash pid ppid uid
+ // W ActivityManager: Stale PhantomProcessRecord {xxxxxxx 7650:1393:clatd-wlan0/1000}, removing
+ // This is due to:
+ // $ adbz shell ls -ld /proc/7650
+ // dr-xr-xr-x 9 clat clat 0 2025-03-14 11:37 /proc/7650
+ // which is used by
+ // //frameworks/base/core/java/com/android/internal/os/ProcessCpuTracker.java
+ // which thus returns the uid 'clat' vs
+ // //frameworks/base/core/java/android/os/Process.java
+ // getUidForPid() which grabs *real* 'uid' from /proc/<pid>/status and is used in:
+ // //frameworks/base/services/core/java/com/android/server/am/PhantomProcessList.java
+ // (perhaps this should grab euid instead? unclear)
+ //
+ // However, we want to drop as many privs as possible, hence:
+ gid_t egid = getegid(); // documented to never fail, hence should return AID_CLAT == 1029
+ uid_t euid = geteuid(); // (ditto)
+ setresgid(egid, egid, egid); // ignore any failure
+ setresuid(euid, euid, euid); // ignore any failure
+ // ideally we'd somehow drop supplementary groups too...
+ // but for historical reasons that actually requires CAP_SETGID which we don't have
+ // (see man 2 setgroups)
+ //
+ // Now we (should) have:
+ // $ adb shell ps | grep clat
+ // clat 5370 1479 10785364 2528 do_sys_poll 0 S clatd-wlan0
+ // # adb shell cat /proc/5370/status | egrep -i '^(Uid:|Gid:|Groups:)'
+ // Uid: 1029 1029 1029 1029
+ // Gid: 1029 1029 1029 1029
+ // Groups: 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1018 1021 1023 1024 1032 1065 3001 3002 3003 3005 3006 3007 3009 3010 3011 3012
+
while ((opt = getopt(argc, argv, "i:p:4:6:t:r:w:h")) != -1) {
switch (opt) {
case 'i':
@@ -150,7 +322,7 @@
exit(1);
}
- logmsg(ANDROID_LOG_INFO, "Starting clat version %s on %s plat=%s v4=%s v6=%s", CLATD_VERSION,
+ logmsg(ANDROID_LOG_INFO, "Starting clat version " CLATD_VERSION " on %s plat=%s v4=%s v6=%s",
uplink_interface, plat_prefix ? plat_prefix : "(none)", v4_addr ? v4_addr : "(none)",
v6_addr ? v6_addr : "(none)");
@@ -183,25 +355,39 @@
}
// Loop until someone sends us a signal or brings down the tun interface.
- if (signal(SIGTERM, stop_loop) == SIG_ERR) {
+ if (signal(SIGTERM, handle_sigterm) == SIG_ERR) {
logmsg(ANDROID_LOG_FATAL, "sigterm handler failed: %s", strerror(errno));
exit(1);
}
+ // Apparently some network gear will refuse to perform NS for IPs that aren't DAD'ed,
+ // this would then result in an ipv6-only network with working native ipv6, working
+ // IPv4 via DNS64, but non-functioning IPv4 via CLAT (ie. IPv4 literals + IPv4 only apps).
+ // The kernel itself doesn't do DAD for anycast ips (but does handle IPV6 MLD and handle ND).
+ // So we'll spoof dad here, and yeah, we really should check for a response and in
+ // case of failure pick a different IP. Seeing as 48-bits of the IP are utterly random
+ // (with the other 16 chosen to guarantee checksum neutrality) this seems like a remote
+ // concern...
+ // TODO: actually perform true DAD
+ send_dad(tunnel.write_fd6, &Global_Clatd_Config.ipv6_local_subnet);
+
+ enable_seccomp(); // WARNING: from this point forward very limited system calls available.
+
event_loop(&tunnel);
- logmsg(ANDROID_LOG_INFO, "Shutting down clat on %s", uplink_interface);
-
- if (running) {
- logmsg(ANDROID_LOG_INFO, "Clatd on %s waiting for SIGTERM", uplink_interface);
+ if (sigterm) {
+ logmsg(ANDROID_LOG_INFO, "Shutting down clatd on %s, already received SIGTERM", uplink_interface);
+ } else {
+ // this implies running == false, ie. we received EOF or ENETDOWN error.
+ logmsg(ANDROID_LOG_INFO, "Shutting down clatd on %s, waiting for SIGTERM", uplink_interface);
// let's give higher level java code 15 seconds to kill us,
// but eventually terminate anyway, in case system server forgets about us...
- // sleep() should be interrupted by SIGTERM, the handler should clear running
+ // sleep() should be interrupted by SIGTERM, the handler should set 'sigterm'
sleep(15);
logmsg(ANDROID_LOG_INFO, "Clatd on %s %s SIGTERM", uplink_interface,
- running ? "timed out waiting for" : "received");
- } else {
- logmsg(ANDROID_LOG_INFO, "Clatd on %s already received SIGTERM", uplink_interface);
+ sigterm ? "received" : "timed out waiting for");
}
- return 0;
+
+ // Using _exit() here avoids 4 mprotect() syscalls triggered via 'exit(0)' or 'return 0'
+ _exit(0);
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 3d7ea69..f8a1293 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -1200,11 +1200,14 @@
/** @hide */
public static final long FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS = 1L;
+ /** @hide */
+ public static final long FEATURE_QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER = 1L << 1;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@LongDef(flag = true, prefix = "FEATURE_", value = {
- FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS
+ FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS,
+ FEATURE_QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER
})
public @interface ConnectivityManagerFeature {}
@@ -1252,9 +1255,13 @@
* {@link ConnectivityManager#registerNetworkAgent}
* @hide
*/
- public static Network registerNetworkAgentResult(
+ public static NetworkAndAgentRegistryParcelable registerNetworkAgentResult(
@Nullable final Network network, @Nullable final INetworkAgentRegistry registry) {
- return network;
+ final NetworkAndAgentRegistryParcelable result =
+ new NetworkAndAgentRegistryParcelable();
+ result.network = network;
+ result.registry = registry;
+ return result;
}
}
@@ -3968,7 +3975,8 @@
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_FACTORY})
- public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni,
+ public NetworkAndAgentRegistryParcelable registerNetworkAgent(
+ @NonNull INetworkAgent na, @NonNull NetworkInfo ni,
@NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
@NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId) {
return registerNetworkAgent(na, ni, lp, nc, null /* localNetworkConfig */, score, config,
@@ -3983,7 +3991,8 @@
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_FACTORY})
- public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni,
+ public NetworkAndAgentRegistryParcelable registerNetworkAgent(
+ @NonNull INetworkAgent na, @NonNull NetworkInfo ni,
@NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
@Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
@NonNull NetworkAgentConfig config, int providerId) {
@@ -4875,7 +4884,8 @@
return 0;
}
- private boolean isFeatureEnabled(@ConnectivityManagerFeature long connectivityManagerFeature) {
+ /** @hide */
+ public boolean isFeatureEnabled(@ConnectivityManagerFeature long connectivityManagerFeature) {
synchronized (mEnabledConnectivityManagerFeaturesLock) {
if (mEnabledConnectivityManagerFeatures == null) {
try {
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 47b3316..a270684 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -30,6 +30,7 @@
import android.net.LocalNetworkConfig;
import android.net.Network;
import android.net.NetworkAgentConfig;
+import android.net.NetworkAndAgentRegistryParcelable;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
@@ -146,7 +147,8 @@
void declareNetworkRequestUnfulfillable(in NetworkRequest request);
- Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp,
+ NetworkAndAgentRegistryParcelable registerNetworkAgent(
+ in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp,
in NetworkCapabilities nc, in NetworkScore score,
in LocalNetworkConfig localNetworkConfig, in NetworkAgentConfig config,
in int factorySerialNumber);
diff --git a/framework/src/android/net/INetworkAgent.aidl b/framework/src/android/net/INetworkAgent.aidl
index fa5175c..c6beeca 100644
--- a/framework/src/android/net/INetworkAgent.aidl
+++ b/framework/src/android/net/INetworkAgent.aidl
@@ -26,7 +26,7 @@
* @hide
*/
oneway interface INetworkAgent {
- void onRegistered(in INetworkAgentRegistry registry);
+ void onRegistered();
void onDisconnected();
void onBandwidthUpdateRequested();
void onValidationStatusChanged(int validationStatus,
diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl
index 61b27b5..afdd1ee 100644
--- a/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -30,7 +30,7 @@
* Interface for NetworkAgents to send network properties.
* @hide
*/
-oneway interface INetworkAgentRegistry {
+interface INetworkAgentRegistry {
void sendNetworkCapabilities(in NetworkCapabilities nc);
void sendLinkProperties(in LinkProperties lp);
// TODO: consider replacing this by "markConnected()" and removing
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index cefa1ea..95b45ce 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -37,6 +37,7 @@
import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.FrameworkConnectivityStatsLog;
@@ -98,6 +99,7 @@
@Nullable
private volatile Network mNetwork;
+ // Null before the agent is registered
@Nullable
private volatile INetworkAgentRegistry mRegistry;
@@ -115,12 +117,32 @@
private final ArrayList<RegistryAction> mPreConnectedQueue = new ArrayList<>();
private volatile long mLastBwRefreshTime = 0;
private static final long BW_REFRESH_MIN_WIN_MS = 500;
+
+ private final boolean mQueueRemoved;
+
private boolean mBandwidthUpdateScheduled = false;
private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false);
@NonNull
private NetworkInfo mNetworkInfo;
@NonNull
private final Object mRegisterLock = new Object();
+ // TODO : when ConnectivityFlags.QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER is
+ // not chickened out this is never read. Remove when retiring this flag.
+ private boolean mConnected = false;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "STATE_" }, value = {
+ STATE_CREATED,
+ STATE_REGISTERED,
+ STATE_UNREGISTERED
+ })
+ public @interface NetworkAgentState {}
+ private static final int STATE_CREATED = 0;
+ private static final int STATE_REGISTERED = 1;
+ private static final int STATE_UNREGISTERED = 2;
+ @GuardedBy("mRegisterLock")
+ private int mState = STATE_CREATED;
/**
* The ID of the {@link NetworkProvider} that created this object, or
@@ -503,6 +525,18 @@
return ni;
}
+ /**
+ * Returns whether a given ConnectivityManager feature is enabled.
+ *
+ * Tests can override this.
+ * @hide
+ */
+ @VisibleForTesting
+ public boolean isFeatureEnabled(@NonNull Context context,
+ @ConnectivityManager.ConnectivityManagerFeature long feature) {
+ return context.getSystemService(ConnectivityManager.class).isFeatureEnabled(feature);
+ }
+
// Temporary backward compatibility constructor
public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
@NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score,
@@ -585,6 +619,10 @@
@Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
@NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) {
mHandler = new NetworkAgentHandler(looper);
+ // If the feature is enabled, then events are queued in the system
+ // server, and it's removed from this NetworkAgent.
+ mQueueRemoved = isFeatureEnabled(context,
+ ConnectivityManager.FEATURE_QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER);
LOG_TAG = logTag;
mNetworkInfo = new NetworkInfo(ni);
this.providerId = providerId;
@@ -606,22 +644,30 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_AGENT_CONNECTED: {
- if (mRegistry != null) {
- log("Received new connection while already connected!");
+ if (mQueueRemoved) {
+ // No handling. This message is legacy from a time where the
+ // agent had to wait until the registry was sent to it, which
+ // would only happen after the corresponding NetworkMonitor
+ // was created.
+ mConnected = true; // never read, but mConnected = false would be confusing
} else {
- if (VDBG) log("NetworkAgent fully connected");
- synchronized (mPreConnectedQueue) {
- final INetworkAgentRegistry registry = (INetworkAgentRegistry) msg.obj;
- mRegistry = registry;
- for (RegistryAction a : mPreConnectedQueue) {
- try {
- a.execute(registry);
- } catch (RemoteException e) {
- Log.wtf(LOG_TAG, "Communication error with registry", e);
- // Fall through
+ // Feature chickened out, keep the old queueing behavior
+ synchronized (mRegisterLock) {
+ if (mConnected) {
+ log("Received new connection while already connected!");
+ } else {
+ if (VDBG) log("NetworkAgent fully connected");
+ for (RegistryAction a : mPreConnectedQueue) {
+ try {
+ a.execute(mRegistry);
+ } catch (RemoteException e) {
+ Log.wtf(LOG_TAG, "Communication error with registry", e);
+ // Fall through
+ }
}
+ mPreConnectedQueue.clear();
}
- mPreConnectedQueue.clear();
+ mConnected = true;
}
}
break;
@@ -630,8 +676,9 @@
if (DBG) log("NetworkAgent channel lost");
// let the client know CS is done with us.
onNetworkUnwanted();
- synchronized (mPreConnectedQueue) {
- mRegistry = null;
+ synchronized (mRegisterLock) {
+ mState = STATE_UNREGISTERED;
+ mConnected = false;
}
break;
}
@@ -753,25 +800,49 @@
public Network register() {
if (VDBG) log("Registering NetworkAgent");
synchronized (mRegisterLock) {
- if (mNetwork != null) {
- throw new IllegalStateException("Agent already registered");
+ if (mQueueRemoved) {
+ switch (mState) {
+ case STATE_REGISTERED:
+ throw new IllegalStateException("Agent already registered");
+ case STATE_UNREGISTERED:
+ throw new IllegalStateException("Agent already unregistered");
+ default: // CREATED, this is the normal case
+ }
+ } else {
+ // Feature is chickened out, do the old processing
+ if (mNetwork != null) {
+ throw new IllegalStateException("Agent already registered");
+ }
}
final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context
.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkAndAgentRegistryParcelable result;
if (mInitialConfiguration.localNetworkConfig == null) {
// Call registerNetworkAgent without localNetworkConfig argument to pass
// android.net.cts.NetworkAgentTest#testAgentStartsInConnecting in old cts
- mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
+ result = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
new NetworkInfo(mInitialConfiguration.info),
mInitialConfiguration.properties, mInitialConfiguration.capabilities,
mInitialConfiguration.score, mInitialConfiguration.config, providerId);
} else {
- mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
+ result = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
new NetworkInfo(mInitialConfiguration.info),
mInitialConfiguration.properties, mInitialConfiguration.capabilities,
mInitialConfiguration.localNetworkConfig, mInitialConfiguration.score,
mInitialConfiguration.config, providerId);
}
+ if (null == result && Process.isApplicationUid(Process.myUid())) {
+ // Let it slide in tests to allow mocking, since NetworkAndAgentRegistryParcelable
+ // is not public and can't be instantiated by CTS. The danger here is that if
+ // this happens in production for some reason the code will crash later instead
+ // of here. If this is a system app, it will still crash as expected.
+ Log.e(LOG_TAG, "registerNetworkAgent returned null. This agent will not work. "
+ + "Is ConnectivityManager a mock ?");
+ } else {
+ mNetwork = result.network;
+ mRegistry = result.registry;
+ mState = STATE_REGISTERED;
+ }
mInitialConfiguration = null; // All this memory can now be GC'd
}
return mNetwork;
@@ -787,8 +858,8 @@
}
@Override
- public void onRegistered(@NonNull INetworkAgentRegistry registry) {
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_CONNECTED, registry));
+ public void onRegistered() {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_CONNECTED));
}
@Override
@@ -913,11 +984,14 @@
*
* @hide
*/
- public INetworkAgent registerForTest(final Network network) {
+ public INetworkAgent registerForTest(final Network network,
+ final INetworkAgentRegistry registry) {
log("Registering NetworkAgent for test");
synchronized (mRegisterLock) {
mNetwork = network;
mInitialConfiguration = null;
+ mRegistry = registry;
+ mState = STATE_REGISTERED;
}
return new NetworkAgentBinder(mHandler);
}
@@ -943,30 +1017,49 @@
return mNetwork;
}
- private void queueOrSendMessage(@NonNull RegistryAction action) {
- synchronized (mPreConnectedQueue) {
- if (mNetwork == null && !Process.isApplicationUid(Process.myUid())) {
- // Theoretically, it should not be valid to queue messages here before
- // registering the NetworkAgent. However, practically, with the way
- // queueing works right now, it ends up working out just fine.
- // Log a statistic so that we know if this is happening in the
- // wild. The check for isApplicationUid is to prevent logging the
- // metric from test code.
+ private void logTerribleErrorMessageBeforeConnect() {
+ FrameworkConnectivityStatsLog.write(
+ FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED,
+ FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_MESSAGE_QUEUED_BEFORE_CONNECT
+ );
+ }
- FrameworkConnectivityStatsLog.write(
- FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED,
- FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_MESSAGE_QUEUED_BEFORE_CONNECT
- );
- }
- if (mRegistry != null) {
- try {
- action.execute(mRegistry);
- } catch (RemoteException e) {
- Log.wtf(LOG_TAG, "Error executing registry action", e);
- // Fall through: the channel is asynchronous and does not report errors back
+ private void send(@NonNull RegistryAction action) {
+ synchronized (mRegisterLock) {
+ if (mQueueRemoved) {
+ if (mState <= STATE_CREATED) {
+ // Log a terrible error. There is nothing to do with this message
+ // so drop it.
+ logTerribleErrorMessageBeforeConnect();
+ Log.e(LOG_TAG, "Agent not yet registered, ignoring command");
+ return;
+ }
+ if (mState >= STATE_UNREGISTERED) {
+ // This should not crash for two reasons : first, the agent may
+ // be disconnected by ConnectivityService at any time and the message
+ // typically arrives on another thread, so it's not feasible for
+ // apps to check before sending, they'd have to always catch. Second,
+ // historically this hasn't thrown and some code may be relying on
+ // the historical behavior.
+ Log.e(LOG_TAG, "Agent already unregistered, ignoring command");
+ return;
}
} else {
- mPreConnectedQueue.add(action);
+ if (null == mNetwork) {
+ // Log a terrible error but still enqueue the message for backward
+ // compatibility.
+ logTerribleErrorMessageBeforeConnect();
+ }
+ if (!mConnected) {
+ mPreConnectedQueue.add(action);
+ return;
+ }
+ }
+ try {
+ action.execute(mRegistry);
+ } catch (RemoteException e) {
+ Log.wtf(LOG_TAG, "Error executing registry action", e);
+ // Fall through: the channel is asynchronous and does not report errors back
}
}
}
@@ -977,8 +1070,9 @@
*/
public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
Objects.requireNonNull(linkProperties);
- final LinkProperties lp = new LinkProperties(linkProperties);
- queueOrSendMessage(reg -> reg.sendLinkProperties(lp));
+ // Copy the object because if the agent is running in the system server
+ // then the same instance will be seen by the registry
+ send(reg -> reg.sendLinkProperties(new LinkProperties(linkProperties)));
}
/**
@@ -1004,7 +1098,7 @@
@SuppressLint("NullableCollection") @Nullable List<Network> underlyingNetworks) {
final ArrayList<Network> underlyingArray = (underlyingNetworks != null)
? new ArrayList<>(underlyingNetworks) : null;
- queueOrSendMessage(reg -> reg.sendUnderlyingNetworks(underlyingArray));
+ send(reg -> reg.sendUnderlyingNetworks(underlyingArray));
}
/**
@@ -1014,7 +1108,7 @@
public void markConnected() {
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null /* reason */,
mNetworkInfo.getExtraInfo());
- queueOrSendNetworkInfo(mNetworkInfo);
+ sendNetworkInfo(mNetworkInfo);
}
/**
@@ -1027,7 +1121,12 @@
// When unregistering an agent nobody should use the extrainfo (or reason) any more.
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null /* reason */,
null /* extraInfo */);
- queueOrSendNetworkInfo(mNetworkInfo);
+ synchronized (mRegisterLock) {
+ if (mState >= STATE_REGISTERED) {
+ sendNetworkInfo(mNetworkInfo);
+ }
+ mState = STATE_UNREGISTERED;
+ }
}
/**
@@ -1050,7 +1149,7 @@
*/
public void setTeardownDelayMillis(
@IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int teardownDelayMillis) {
- queueOrSendMessage(reg -> reg.sendTeardownDelayMs(teardownDelayMillis));
+ send(reg -> reg.sendTeardownDelayMs(teardownDelayMillis));
}
/**
@@ -1089,7 +1188,7 @@
*/
public void unregisterAfterReplacement(
@IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int timeoutMillis) {
- queueOrSendMessage(reg -> reg.sendUnregisterAfterReplacement(timeoutMillis));
+ send(reg -> reg.sendUnregisterAfterReplacement(timeoutMillis));
}
/**
@@ -1107,7 +1206,7 @@
@SystemApi
public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) {
mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName);
- queueOrSendNetworkInfo(mNetworkInfo);
+ sendNetworkInfo(mNetworkInfo);
}
/**
@@ -1129,7 +1228,7 @@
@Deprecated
public void setLegacyExtraInfo(@Nullable final String extraInfo) {
mNetworkInfo.setExtraInfo(extraInfo);
- queueOrSendNetworkInfo(mNetworkInfo);
+ sendNetworkInfo(mNetworkInfo);
}
/**
@@ -1137,13 +1236,9 @@
* @hide TODO: expose something better.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- public final void sendNetworkInfo(NetworkInfo networkInfo) {
- queueOrSendNetworkInfo(networkInfo);
- }
-
- private void queueOrSendNetworkInfo(NetworkInfo networkInfo) {
+ private void sendNetworkInfo(final NetworkInfo networkInfo) {
final NetworkInfo ni = new NetworkInfo(networkInfo);
- queueOrSendMessage(reg -> reg.sendNetworkInfo(ni));
+ send(reg -> reg.sendNetworkInfo(ni));
}
/**
@@ -1156,7 +1251,7 @@
mLastBwRefreshTime = System.currentTimeMillis();
final NetworkCapabilities nc =
new NetworkCapabilities(networkCapabilities, NetworkCapabilities.REDACT_NONE);
- queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc));
+ send(reg -> reg.sendNetworkCapabilities(nc));
}
/**
@@ -1168,7 +1263,7 @@
Objects.requireNonNull(config);
// If the agent doesn't have NET_CAPABILITY_LOCAL_NETWORK, this will be ignored by
// ConnectivityService with a Log.wtf.
- queueOrSendMessage(reg -> reg.sendLocalNetworkConfig(config));
+ send(reg -> reg.sendLocalNetworkConfig(config));
}
/**
@@ -1178,7 +1273,7 @@
*/
public void sendNetworkScore(@NonNull NetworkScore score) {
Objects.requireNonNull(score);
- queueOrSendMessage(reg -> reg.sendScore(score));
+ send(reg -> reg.sendScore(score));
}
/**
@@ -1228,8 +1323,7 @@
* @hide should move to NetworkAgentConfig.
*/
public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
- queueOrSendMessage(reg -> reg.sendExplicitlySelected(
- explicitlySelected, acceptUnvalidated));
+ send(reg -> reg.sendExplicitlySelected(explicitlySelected, acceptUnvalidated));
}
/**
@@ -1369,7 +1463,7 @@
*/
public final void sendSocketKeepaliveEvent(int slot,
@SocketKeepalive.KeepaliveEvent int event) {
- queueOrSendMessage(reg -> reg.sendSocketKeepaliveEvent(slot, event));
+ send(reg -> reg.sendSocketKeepaliveEvent(slot, event));
}
/** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */
public void onSocketKeepaliveEvent(int slot, int reason) {
@@ -1475,11 +1569,11 @@
@NonNull final QosSessionAttributes attributes) {
Objects.requireNonNull(attributes, "The attributes must be non-null");
if (attributes instanceof EpsBearerQosSessionAttributes) {
- queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId,
+ send(reg -> reg.sendEpsQosSessionAvailable(qosCallbackId,
new QosSession(sessionId, QosSession.TYPE_EPS_BEARER),
(EpsBearerQosSessionAttributes)attributes));
} else if (attributes instanceof NrQosSessionAttributes) {
- queueOrSendMessage(ra -> ra.sendNrQosSessionAvailable(qosCallbackId,
+ send(reg -> reg.sendNrQosSessionAvailable(qosCallbackId,
new QosSession(sessionId, QosSession.TYPE_NR_BEARER),
(NrQosSessionAttributes)attributes));
}
@@ -1494,7 +1588,7 @@
*/
public final void sendQosSessionLost(final int qosCallbackId,
final int sessionId, final int qosSessionType) {
- queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId,
+ send(reg -> reg.sendQosSessionLost(qosCallbackId,
new QosSession(sessionId, qosSessionType)));
}
@@ -1508,7 +1602,7 @@
*/
public final void sendQosCallbackError(final int qosCallbackId,
@QosCallbackException.ExceptionType final int exceptionType) {
- queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType));
+ send(reg -> reg.sendQosCallbackError(qosCallbackId, exceptionType));
}
/**
@@ -1525,7 +1619,7 @@
throw new IllegalArgumentException("Duration must be within ["
+ MIN_LINGER_TIMER_MS + "," + Integer.MAX_VALUE + "]ms");
}
- queueOrSendMessage(ra -> ra.sendLingerDuration((int) durationMs));
+ send(reg -> reg.sendLingerDuration((int) durationMs));
}
/**
@@ -1534,7 +1628,7 @@
*/
public void sendAddDscpPolicy(@NonNull final DscpPolicy policy) {
Objects.requireNonNull(policy);
- queueOrSendMessage(ra -> ra.sendAddDscpPolicy(policy));
+ send(reg -> reg.sendAddDscpPolicy(policy));
}
/**
@@ -1542,14 +1636,14 @@
* @param policyId the ID corresponding to a specific DSCP Policy.
*/
public void sendRemoveDscpPolicy(final int policyId) {
- queueOrSendMessage(ra -> ra.sendRemoveDscpPolicy(policyId));
+ send(reg -> reg.sendRemoveDscpPolicy(policyId));
}
/**
* Remove all the DSCP policies on this network.
*/
public void sendRemoveAllDscpPolicies() {
- queueOrSendMessage(ra -> ra.sendRemoveAllDscpPolicies());
+ send(reg -> reg.sendRemoveAllDscpPolicies());
}
/** @hide */
diff --git a/framework/src/android/net/NetworkAndAgentRegistryParcelable.aidl b/framework/src/android/net/NetworkAndAgentRegistryParcelable.aidl
new file mode 100644
index 0000000..8c01bbc
--- /dev/null
+++ b/framework/src/android/net/NetworkAndAgentRegistryParcelable.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.INetworkAgentRegistry;
+import android.net.Network;
+
+/**
+ * A pair of Network and NetworkAgentRegistry.
+ *
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable NetworkAndAgentRegistryParcelable {
+ Network network;
+ INetworkAgentRegistry registry;
+}
diff --git a/networksecurity/TEST_MAPPING b/networksecurity/TEST_MAPPING
index 448ee84..f75bf9a 100644
--- a/networksecurity/TEST_MAPPING
+++ b/networksecurity/TEST_MAPPING
@@ -1,4 +1,9 @@
{
+ "tethering-mainline-presubmit": [
+ {
+ "name": "NetworkSecurityUnitTests"
+ }
+ ],
"presubmit": [
{
"name": "CtsNetSecConfigCertificateTransparencyTestCases"
diff --git a/networksecurity/tests/unit/Android.bp b/networksecurity/tests/unit/Android.bp
index 11263cf..1336acc 100644
--- a/networksecurity/tests/unit/Android.bp
+++ b/networksecurity/tests/unit/Android.bp
@@ -41,4 +41,5 @@
],
sdk_version: "test_current",
+ min_sdk_version: "VanillaIceCream",
}
diff --git a/networksecurity/tests/unit/AndroidTest.xml b/networksecurity/tests/unit/AndroidTest.xml
index 3c94df7..fc0943e 100644
--- a/networksecurity/tests/unit/AndroidTest.xml
+++ b/networksecurity/tests/unit/AndroidTest.xml
@@ -17,6 +17,12 @@
<configuration description="Runs NetworkSecurity Mainline unit Tests.">
<option name="test-tag" value="NetworkSecurityUnitTests" />
+ <!--
+ Only run tests if the device under test is SDK version 35 (Android 15) or above.
+ -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.Sdk35ModuleController" />
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="NetworkSecurityUnitTests.apk" />
</target_preparer>
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 25c0617..9bd407d 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -915,10 +915,13 @@
final InetAddress address, final int protocol, final int remotePort,
final boolean isAllowed) {
throwIfPre25Q2("addLocalNetAccess is not available on pre-B devices");
- final int ifIndex;
if (iface == null) {
- ifIndex = 0;
- } else {
+ Log.e(TAG, "Null iface, skip addLocalNetAccess for " + address);
+ return;
+ }
+ int ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
+ if (ifIndex == 0) {
+ mInterfaceTracker.addInterface(iface);
ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
}
if (ifIndex == 0) {
@@ -1325,11 +1328,11 @@
+ value.iif2 + "(" + mDeps.getIfName(value.iif2) + ")");
if (sLocalNetBlockedUidMap != null) {
BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap",
- (key, value) -> "[" + key + "]: " + value);
+ (key, value) -> "" + key + ": " + value.val);
}
if (sLocalNetBlockedUidMap != null) {
BpfDump.dumpMap(sLocalNetBlockedUidMap, pw, "sLocalNetBlockedUidMap",
- (key, value) -> "[" + key + "]: " + value);
+ (key, value) -> "" + key + ": " + value.val);
}
dumpDataSaverConfig(pw);
pw.decreaseIndent();
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 3ce3f02..dc4a35b 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -158,6 +158,7 @@
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
import static com.android.server.connectivity.ConnectivityFlags.NAMESPACE_TETHERING_BOOT;
import static com.android.server.connectivity.ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS;
+import static com.android.server.connectivity.ConnectivityFlags.QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER;
import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import static com.android.server.connectivity.ConnectivityFlags.WIFI_DATA_INACTIVITY_TIMEOUT;
@@ -228,6 +229,7 @@
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
+import android.net.NetworkAndAgentRegistryParcelable;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
@@ -526,6 +528,7 @@
private final boolean mBackgroundFirewallChainEnabled;
private final boolean mUseDeclaredMethodsForCallbacksEnabled;
+ private final boolean mQueueNetworkAgentEventsInSystemServer;
// Flag to delay callbacks for frozen apps, suppressing duplicate and stale callbacks.
private final boolean mQueueCallbacksForFrozenApps;
@@ -1928,6 +1931,9 @@
mUseDeclaredMethodsForCallbacksEnabled =
mDeps.isFeatureNotChickenedOut(context,
ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS);
+ mQueueNetworkAgentEventsInSystemServer =
+ mDeps.isFeatureNotChickenedOut(context,
+ ConnectivityFlags.QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER);
// registerUidFrozenStateChangedCallback is only available on U+
mQueueCallbacksForFrozenApps = mDeps.isAtLeastU()
&& mDeps.isFeatureNotChickenedOut(context, QUEUE_CALLBACKS_FOR_FROZEN_APPS);
@@ -4688,15 +4694,35 @@
private void maybeHandleNetworkAgentMessage(Message msg) {
final Pair<NetworkAgentInfo, Object> arg = (Pair<NetworkAgentInfo, Object>) msg.obj;
final NetworkAgentInfo nai = arg.first;
- if (!mNetworkAgentInfos.contains(nai)) {
- if (VDBG) {
- log(String.format("%s from unknown NetworkAgent", eventName(msg.what)));
+
+ // If the network has been destroyed, the only thing that it can do is disconnect.
+ if (nai.isDestroyed() && !isDisconnectRequest(msg)) {
+ if (DBG) {
+ log("Message " + eventName(msg.what) + " from destroyed agent with netId "
+ + nai.network.netId);
}
return;
}
- // If the network has been destroyed, the only thing that it can do is disconnect.
- if (nai.isDestroyed() && !isDisconnectRequest(msg)) {
+ if (mQueueNetworkAgentEventsInSystemServer && nai.maybeEnqueueMessage(msg)) {
+ // If the message is enqueued, the NAI will replay it immediately
+ // when registration is complete. It does this by sending all the
+ // messages in the order received immediately after the
+ // EVENT_AGENT_REGISTERED message.
+ if (DBG) {
+ log("Message " + eventName(msg.what) + " enqueued for agent with netId "
+ + nai.network.netId);
+ }
+ return;
+ }
+
+ // If the nai has been registered (and doesn't enqueue), it should now be
+ // in the list of NAIs.
+ if (!mNetworkAgentInfos.contains(nai)) {
+ // TODO : this is supposed to be impossible
+ if (VDBG) {
+ log(String.format("%s from unknown NetworkAgent", eventName(msg.what)));
+ }
return;
}
@@ -7494,7 +7520,7 @@
private boolean isLegacyLockdownNai(NetworkAgentInfo nai) {
return mLockdownEnabled
- && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY
+ && isLegacyVpn(nai)
&& nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID);
}
@@ -9328,7 +9354,7 @@
* @param providerId the ID of the provider owning this NetworkAgent.
* @return the network created for this agent.
*/
- public Network registerNetworkAgent(INetworkAgent na,
+ public NetworkAndAgentRegistryParcelable registerNetworkAgent(INetworkAgent na,
NetworkInfo networkInfo,
LinkProperties linkProperties,
NetworkCapabilities networkCapabilities,
@@ -9371,7 +9397,8 @@
}
}
- private Network registerNetworkAgentInternal(INetworkAgent na, NetworkInfo networkInfo,
+ private NetworkAndAgentRegistryParcelable registerNetworkAgentInternal(
+ INetworkAgent na, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
NetworkScore currentScore, NetworkAgentConfig networkAgentConfig,
@Nullable LocalNetworkConfig localNetworkConfig, int providerId,
@@ -9400,11 +9427,15 @@
if (DBG) log("registerNetworkAgent " + nai);
mDeps.getNetworkStack().makeNetworkMonitor(
nai.network, name, new NetworkMonitorCallbacks(nai));
- // NetworkAgentInfo registration will finish when the NetworkMonitor is created.
- // If the network disconnects or sends any other event before that, messages are deferred by
- // NetworkAgent until nai.connect(), which will be called when finalizing the
- // registration.
- return nai.network;
+ // NetworkAgentInfo registration is done, but CS will only accept messages when the
+ // NetworkMonitor is created. If the network disconnects or sends any other event
+ // before that, messages are deferred by the Tracker Handler until it is (by asking
+ // NetworkAgentInfo to do it). The window is very small unless the NetworkStack
+ // doesn't reply immediately, which would mean a broken system anyway.
+ final NetworkAndAgentRegistryParcelable result = new NetworkAndAgentRegistryParcelable();
+ result.network = nai.network;
+ result.registry = nai.getRegistry();
+ return result;
}
private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
@@ -9416,8 +9447,6 @@
nai.getDeclaredCapabilitiesSanitized(mCarrierPrivilegeAuthenticator)));
processLinkPropertiesFromAgent(nai, nai.linkProperties);
- nai.onNetworkMonitorCreated(networkMonitor);
-
mNetworkAgentInfos.add(nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.put(nai.network.getNetId(), nai);
@@ -9432,10 +9461,11 @@
if (nai.isLocalNetwork()) {
handleUpdateLocalNetworkConfig(nai, null /* oldConfig */, nai.localNetworkConfig);
}
- nai.notifyRegistered();
+ nai.notifyRegistered(networkMonitor);
NetworkInfo networkInfo = nai.networkInfo;
updateNetworkInfo(nai, networkInfo);
updateVpnUids(nai, null, nai.networkCapabilities);
+ nai.processEnqueuedMessages(mTrackerHandler::handleMessage);
}
private class NetworkOfferInfo implements IBinder.DeathRecipient {
@@ -10133,8 +10163,8 @@
* interfaces.
* Ingress discard rule is added to the address iff
* 1. The address is not a link local address
- * 2. The address is used by a single interface of VPN whose VPN type is not TYPE_VPN_LEGACY
- * or TYPE_VPN_OEM and the address is not used by any other interfaces even non-VPN ones
+ * 2. The address is used by a single interface of VPN whose VPN type is not LEGACY, OEM or
+ * OEM_LEGACY and the address is not used by any other interfaces even non-VPN ones
* Ingress discard rule is not be added to TYPE_VPN_LEGACY or TYPE_VPN_OEM VPN since these VPNs
* might need to receive packet to VPN address via non-VPN interface.
* This method can be called during network disconnects, when nai has already been removed from
@@ -10172,9 +10202,7 @@
final Set<Pair<InetAddress, String>> ingressDiscardRules = new ArraySet<>();
for (final NetworkAgentInfo agent : nais) {
final int vpnType = getVpnType(agent);
- if (!agent.isVPN() || agent.isDestroyed()
- || vpnType == VpnManager.TYPE_VPN_LEGACY
- || vpnType == VpnManager.TYPE_VPN_OEM) {
+ if (!agent.isVPN() || agent.isDestroyed() || !vpnSupportsInterfaceFiltering(agent)) {
continue;
}
final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties;
@@ -12817,6 +12845,23 @@
return ((VpnTransportInfo) ti).getType();
}
+ private boolean isVpnServiceVpn(NetworkAgentInfo nai) {
+ final int vpnType = getVpnType(nai);
+ return vpnType == VpnManager.TYPE_VPN_SERVICE || vpnType == VpnManager.TYPE_VPN_OEM_SERVICE;
+ }
+
+ private boolean isLegacyVpn(NetworkAgentInfo nai) {
+ final int vpnType = getVpnType(nai);
+ return vpnType == VpnManager.TYPE_VPN_LEGACY || vpnType == VpnManager.TYPE_VPN_OEM_LEGACY;
+ }
+
+ private boolean vpnSupportsInterfaceFiltering(NetworkAgentInfo vpn) {
+ final int vpnType = getVpnType(vpn);
+ return vpnType != VpnManager.TYPE_VPN_LEGACY
+ && vpnType != VpnManager.TYPE_VPN_OEM
+ && vpnType != VpnManager.TYPE_VPN_OEM_LEGACY;
+ }
+
private void maybeUpdateWifiRoamTimestamp(@NonNull NetworkAgentInfo nai,
@NonNull NetworkCapabilities nc) {
final TransportInfo prevInfo = nai.networkCapabilities.getTransportInfo();
@@ -12850,7 +12895,7 @@
if (hasNetworkStackPermission()) return uid;
final NetworkAgentInfo vpn = getVpnForUid(uid);
- if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE
+ if (vpn == null || !isVpnServiceVpn(vpn)
|| vpn.networkCapabilities.getOwnerUid() != mDeps.getCallingUid()) {
return INVALID_UID;
}
@@ -15014,6 +15059,9 @@
if (mUseDeclaredMethodsForCallbacksEnabled) {
features |= ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS;
}
+ if (mQueueNetworkAgentEventsInSystemServer) {
+ features |= ConnectivityManager.FEATURE_QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER;
+ }
return features;
}
@@ -15022,6 +15070,8 @@
switch (featureFlag) {
case INGRESS_TO_VPN_ADDRESS_FILTERING:
return mIngressToVpnAddressFiltering;
+ case QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER:
+ return mQueueNetworkAgentEventsInSystemServer;
default:
throw new IllegalArgumentException("Unknown flag: " + featureFlag);
}
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 136ea81..74bd235 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -62,6 +62,9 @@
public static final String QUEUE_CALLBACKS_FOR_FROZEN_APPS =
"queue_callbacks_for_frozen_apps";
+ public static final String QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER =
+ "queue_network_agent_events_in_system_server";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index e762a8e..abab6ab 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -25,8 +25,8 @@
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.transportNamesOf;
-import static android.system.OsConstants.EIO;
import static android.system.OsConstants.EEXIST;
+import static android.system.OsConstants.EIO;
import static android.system.OsConstants.ENOENT;
import static com.android.net.module.util.FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED;
@@ -62,6 +62,7 @@
import android.net.TcpKeepalivePacketData;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
@@ -92,6 +93,7 @@
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.function.Consumer;
/**
* A bag class used by ConnectivityService for holding a collection of most recent
@@ -630,6 +632,7 @@
// Used by ConnectivityService to keep track of 464xlat.
public final Nat464Xlat clatd;
+ private final ArrayList<Message> mMessagesPendingRegistration = new ArrayList<>();
// Set after asynchronous creation of the NetworkMonitor.
private volatile NetworkMonitorManager mNetworkMonitor;
@@ -639,6 +642,7 @@
private final ConnectivityService.Dependencies mConnServiceDeps;
private final Context mContext;
private final Handler mHandler;
+ private final NetworkAgentMessageHandler mRegistry;
private final QosCallbackTracker mQosCallbackTracker;
private final INetd mNetd;
@@ -673,6 +677,7 @@
mNetd = netd;
mContext = context;
mHandler = handler;
+ mRegistry = new NetworkAgentMessageHandler(mHandler);
this.factorySerialNumber = factorySerialNumber;
this.creatorUid = creatorUid;
mLingerDurationMs = lingerDurationMs;
@@ -698,10 +703,12 @@
* Must be called from the ConnectivityService handler thread. A NetworkAgent can only be
* registered once.
*/
- public void notifyRegistered() {
+ public void notifyRegistered(final INetworkMonitor nm) {
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
+ mNetworkMonitor = new NetworkMonitorManager(nm);
try {
networkAgent.asBinder().linkToDeath(mDeathMonitor, 0);
- networkAgent.onRegistered(new NetworkAgentMessageHandler(mHandler));
+ networkAgent.onRegistered();
} catch (RemoteException e) {
Log.e(TAG, "Error registering NetworkAgent", e);
maybeUnlinkDeathMonitor();
@@ -714,6 +721,41 @@
}
/**
+ * Pass all enqueued messages to the message processor argument, and clear the queue.
+ *
+ * This is called by ConnectivityService when it is ready to receive messages for this
+ * network agent. The processor may process the messages synchronously or asynchronously
+ * at its option.
+ *
+ * @param messageProcessor a function to process the messages
+ */
+ public void processEnqueuedMessages(final Consumer<Message> messageProcessor) {
+ for (final Message enqueued : mMessagesPendingRegistration) {
+ messageProcessor.accept(enqueued);
+ }
+ mMessagesPendingRegistration.clear();
+ }
+
+ /**
+ * Enqueues a message if it needs to be enqueued, and returns whether it was enqueued.
+ *
+ * The message is enqueued iff it can't be sent just yet. If it can be sent
+ * immediately, this method returns false and doesn't enqueue.
+ *
+ * If it enqueues, this method will make a copy of the message for enqueuing since
+ * messages can't be reused or recycled before the end of their processing by the
+ * handler.
+ */
+ public boolean maybeEnqueueMessage(final Message msg) {
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
+ if (null != mNetworkMonitor) return false;
+ final Message m = mHandler.obtainMessage();
+ m.copyFrom(msg);
+ mMessagesPendingRegistration.add(m);
+ return true;
+ }
+
+ /**
* Disconnect the NetworkAgent. Must be called from the ConnectivityService handler thread.
*/
public void disconnect() {
@@ -1036,13 +1078,6 @@
}
/**
- * Inform NetworkAgentInfo that a new NetworkMonitor was created.
- */
- public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
- mNetworkMonitor = new NetworkMonitorManager(networkMonitor);
- }
-
- /**
* Set the NetworkCapabilities on this NetworkAgentInfo. Also attempts to notify NetworkMonitor
* of the new capabilities, if NetworkMonitor has been created.
*
@@ -1117,6 +1152,13 @@
return mNetworkMonitor;
}
+ /**
+ * Get the registry in this NetworkAgentInfo.
+ */
+ public INetworkAgentRegistry getRegistry() {
+ return mRegistry;
+ }
+
// Functions for manipulating the requests satisfied by this network.
//
// These functions must only called on ConnectivityService's main thread.
diff --git a/staticlibs/framework/com/android/net/module/util/SdkUtil.java b/staticlibs/framework/com/android/net/module/util/SdkUtil.java
index 5006ba9..63558dd 100644
--- a/staticlibs/framework/com/android/net/module/util/SdkUtil.java
+++ b/staticlibs/framework/com/android/net/module/util/SdkUtil.java
@@ -16,7 +16,10 @@
package com.android.net.module.util;
+import static android.os.Build.VERSION.SDK_INT;
+
import android.annotation.Nullable;
+import android.os.Build;
/**
* Utilities to deal with multiple SDKs in a single mainline module.
@@ -46,4 +49,9 @@
this.value = value;
}
}
+
+ /** Checks if the device is running on a release version of Android Baklava or newer */
+ public static boolean isAtLeast25Q2() {
+ return SDK_INT >= 36 || (SDK_INT == 35 && "Baklava".equals(Build.VERSION.CODENAME));
+ }
}
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 03f5f06..9222b17 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-V16-java",
+ "netd_aidl_interface-V17-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-V16-ndk",
+ "netd_aidl_interface-V17-ndk",
],
apex_available: [
"com.android.resolv",
@@ -56,12 +56,12 @@
cc_defaults {
name: "netd_aidl_interface_lateststable_cpp_static",
- static_libs: ["netd_aidl_interface-V16-cpp"],
+ static_libs: ["netd_aidl_interface-V17-cpp"],
}
cc_defaults {
name: "netd_aidl_interface_lateststable_cpp_shared",
- shared_libs: ["netd_aidl_interface-V16-cpp"],
+ shared_libs: ["netd_aidl_interface-V17-cpp"],
}
aidl_interface {
@@ -175,6 +175,10 @@
version: "16",
imports: [],
},
+ {
+ version: "17",
+ imports: [],
+ },
],
frozen: true,
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/17/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/17/.hash
new file mode 100644
index 0000000..f69c88b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/.hash
@@ -0,0 +1 @@
+a786da2fe41bda627a8c1e63b99264a415e769c8
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/INetd.aidl
new file mode 100644
index 0000000..8351b56
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/INetd.aidl
@@ -0,0 +1,272 @@
+/**
+ * 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;
+ /**
+ * @deprecated usage is internal to module.
+ */
+ const int NO_PERMISSIONS = 0;
+ /**
+ * @deprecated usage is internal to module.
+ */
+ const int PERMISSION_INTERNET = 4;
+ /**
+ * @deprecated usage is internal to module.
+ */
+ const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+ /**
+ * @deprecated usage is internal to module.
+ */
+ 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/17/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/IpSecMigrateInfoParcel.aidl
new file mode 100644
index 0000000..975a261
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..e77a143
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..a6af5f7
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/NativeVpnType.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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,
+ OEM_SERVICE = 5,
+ OEM_LEGACY = 6,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/17/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/17/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/17/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/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeVpnType.aidl
index 8a8be83..a6af5f7 100644
--- a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeVpnType.aidl
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeVpnType.aidl
@@ -38,4 +38,6 @@
PLATFORM = 2,
LEGACY = 3,
OEM = 4,
+ OEM_SERVICE = 5,
+ OEM_LEGACY = 6,
}
diff --git a/staticlibs/netd/binder/android/net/NativeVpnType.aidl b/staticlibs/netd/binder/android/net/NativeVpnType.aidl
index cd1b447..aa0fdc1 100644
--- a/staticlibs/netd/binder/android/net/NativeVpnType.aidl
+++ b/staticlibs/netd/binder/android/net/NativeVpnType.aidl
@@ -34,7 +34,20 @@
LEGACY = 3,
/**
- * An VPN created by OEM code through other means than VpnService or VpnManager.
+ * A VPN created by OEM code through other means than VpnService or VpnManager.
*/
OEM = 4,
-}
\ No newline at end of file
+
+ /**
+ * A VPN created by OEM code using VpnService, and which OEM code desires to differentiate from
+ * other VPN types. The core networking stack will treat this VPN type similarly to SERVICE.
+ */
+ OEM_SERVICE = 5,
+
+ /**
+ * A VPN created by OEM code using the legacy VPN mechanisms, and which OEM code desires to
+ * differentiate from other VPN types. The core networking stack will treat this VPN type
+ * similarly to LEGACY.
+ */
+ OEM_LEGACY = 6,
+}
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index 348df3b..d4753b7 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -26,7 +26,7 @@
get_apf_counter,
get_apf_counters_from_dumpsys,
get_ipv4_addresses,
- get_ipv6_addresses,
+ get_non_tentative_ipv6_addresses,
get_hardware_address,
is_send_raw_packet_downstream_supported,
is_packet_capture_supported,
@@ -144,7 +144,7 @@
asserts.assert_equal(ip_addresses, [])
@patch("net_tests_utils.host.python.adb_utils.adb_shell")
- def test_get_ipv6_addresses_success(
+ def test_get_non_tentative_ipv6_addresses_success(
self, mock_adb_shell: MagicMock
) -> None:
mock_adb_shell.return_value = """
@@ -156,7 +156,7 @@
inet6 fe80::3aff:2199:2d8e:20d1/64 scope link noprefixroute
valid_lft forever preferred_lft forever
"""
- ip_addresses = get_ipv6_addresses(self.mock_ad, "wlan0")
+ ip_addresses = get_non_tentative_ipv6_addresses(self.mock_ad, "wlan0")
asserts.assert_equal(ip_addresses,
["fe80::10a3:5dff:fe52:de32",
"2001:b400:e53f:164e:9c1e:780e:d1:4658",
@@ -167,7 +167,7 @@
self, mock_adb_shell: MagicMock
) -> None:
mock_adb_shell.return_value = ""
- ip_addresses = get_ipv6_addresses(self.mock_ad, "wlan0")
+ ip_addresses = get_non_tentative_ipv6_addresses(self.mock_ad, "wlan0")
asserts.assert_equal(ip_addresses, [])
@patch("net_tests_utils.host.python.adb_utils.adb_shell")
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
index ae0de79..c9d2527 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
@@ -18,20 +18,28 @@
import android.Manifest.permission.MODIFY_PHONE_STATE
import android.Manifest.permission.READ_PHONE_STATE
+import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.PackageManager
import android.os.ConditionVariable
+import android.os.ParcelFileDescriptor
import android.os.PersistableBundle
+import android.os.Process
import android.telephony.CarrierConfigManager
import android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
+import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.CarrierPrivilegesCallback
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.modules.utils.build.SdkLevel.isAtLeastU
+import com.android.modules.utils.build.SdkLevel
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
+import java.security.MessageDigest
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.junit.rules.TestRule
@@ -46,12 +54,20 @@
* configuration automatically on teardown.
*/
class CarrierConfigRule : TestRule {
+ private val HEX_CHARS: CharArray = charArrayOf(
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ )
+
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+ private val uiAutomation by lazy { InstrumentationRegistry.getInstrumentation().uiAutomation }
private val ccm by lazy { context.getSystemService(CarrierConfigManager::class.java) }
// Map of (subId) -> (original values of overridden settings)
private val originalConfigs = mutableMapOf<Int, PersistableBundle>()
+ // Map of (subId) -> (original values of carrier service package)
+ private val originalCarrierServicePackages = mutableMapOf<Int, String?>()
+
override fun apply(base: Statement, description: Description): Statement {
return CarrierConfigStatement(base, description)
}
@@ -118,6 +134,177 @@
}
}
+ private fun runShellCommand(cmd: String) {
+ val fd: ParcelFileDescriptor = uiAutomation.executeShellCommand(cmd)
+ fd.close() // Don't care about the output.
+ }
+
+ /**
+ * Converts a byte array into a String of hexadecimal characters.
+ *
+ * @param bytes an array of bytes
+ * @return hex string representation of bytes array
+ */
+ private fun bytesToHexString(bytes: ByteArray?): String? {
+ if (bytes == null) return null
+
+ val ret = StringBuilder(2 * bytes.size)
+
+ for (i in bytes.indices) {
+ var b: Int
+ b = 0x0f and (bytes[i].toInt() shr 4)
+ ret.append(HEX_CHARS[b])
+ b = 0x0f and bytes[i].toInt()
+ ret.append(HEX_CHARS[b])
+ }
+
+ return ret.toString()
+ }
+
+ private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
+ if (!SdkLevel.isAtLeastT()) {
+ throw UnsupportedOperationException(
+ "Acquiring carrier privilege requires at least T SDK"
+ )
+ }
+
+ fun getCertHash(): String {
+ val pkgInfo = context.packageManager.getPackageInfo(
+ context.opPackageName,
+ PackageManager.GET_SIGNATURES
+ )
+ val digest = MessageDigest.getInstance("SHA-256")
+ val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray())
+ return bytesToHexString(certHash)!!
+ }
+
+ val tm = context.getSystemService(TelephonyManager::class.java)!!
+
+ val cv = ConditionVariable()
+ val cpb = PrivilegeWaiterCallback(cv)
+ // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
+ // T. This means the lambda will compile as a private method of this class taking a
+ // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
+ // including private methods, this would fail with a link error when running on S-.
+ // To solve this, make the lambda serializable, which causes the compiler to emit a
+ // synthetic class instead of a synthetic method.
+ tryTest @JvmSerializableLambda {
+ val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
+ runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+ tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
+ }
+ // Wait for the callback to be registered
+ assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+ "Can't register CarrierPrivilegesCallback")
+ if (cpb.hasPrivilege == hold) {
+ if (hold) {
+ Log.w(TAG, "Package ${context.opPackageName} already is privileged")
+ } else {
+ Log.w(TAG, "Package ${context.opPackageName} already isn't privileged")
+ }
+ return@tryTest
+ }
+ if (hold) {
+ addConfigOverrides(subId, PersistableBundle().also {
+ it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
+ arrayOf(getCertHash()))
+ })
+ } else {
+ cleanUpNow()
+ }
+ } cleanup @JvmSerializableLambda {
+ runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+ tm.unregisterCarrierPrivilegesCallback(cpb)
+ }
+ }
+ }
+
+ /**
+ * Acquires carrier privilege on the given subscription ID.
+ */
+ fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId)
+
+ /**
+ * Drops carrier privilege from the given subscription ID.
+ */
+ fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId)
+
+ /**
+ * Sets the carrier service package override for the given subscription ID. A null argument will
+ * clear any previously-set override.
+ */
+ fun setCarrierServicePackageOverride(subId: Int, pkg: String?) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw UnsupportedOperationException(
+ "Setting carrier service package override requires at least U SDK"
+ )
+ }
+
+ val tm = context.getSystemService(TelephonyManager::class.java)!!
+
+ val cv = ConditionVariable()
+ val cpb = CarrierServiceChangedWaiterCallback(cv)
+ // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
+ // T. This means the lambda will compile as a private method of this class taking a
+ // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
+ // including private methods, this would fail with a link error when running on S-.
+ // To solve this, make the lambda serializable, which causes the compiler to emit a
+ // synthetic class instead of a synthetic method.
+ tryTest @JvmSerializableLambda {
+ val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
+ runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+ tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
+ }
+ // Wait for the callback to be registered
+ assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+ "Can't register CarrierPrivilegesCallback")
+ if (cpb.pkgName == pkg) {
+ Log.w(TAG, "Carrier service package was already $pkg")
+ return@tryTest
+ }
+ if (!originalCarrierServicePackages.contains(subId)) {
+ originalCarrierServicePackages.put(subId, cpb.pkgName)
+ }
+ cv.close()
+ runAsShell(MODIFY_PHONE_STATE) {
+ if (null == pkg) {
+ // There is a bug in clear-carrier-service-package-override where not adding
+ // the -s argument will use the wrong slot index : b/299604822
+ runShellCommand("cmd phone clear-carrier-service-package-override" +
+ " -s $subId")
+ } else {
+ runShellCommand("cmd phone set-carrier-service-package-override $pkg" +
+ " -s $subId")
+ }
+ }
+ assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+ "Can't modify carrier service package")
+ } cleanup @JvmSerializableLambda {
+ runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+ tm.unregisterCarrierPrivilegesCallback(cpb)
+ }
+ }
+ }
+
+ private class PrivilegeWaiterCallback(private val cv: ConditionVariable) :
+ CarrierPrivilegesCallback {
+ var hasPrivilege = false
+ override fun onCarrierPrivilegesChanged(p: MutableSet<String>, uids: MutableSet<Int>) {
+ hasPrivilege = uids.contains(Process.myUid())
+ cv.open()
+ }
+ }
+
+ private class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) :
+ CarrierPrivilegesCallback {
+ var pkgName: String? = null
+ override fun onCarrierPrivilegesChanged(p: MutableSet<String>, u: MutableSet<Int>) {}
+ override fun onCarrierServiceChanged(pkgName: String?, uid: Int) {
+ this.pkgName = pkgName
+ cv.open()
+ }
+ }
+
/**
* Cleanup overrides that were added by the test case.
*
@@ -138,6 +325,10 @@
}
originalConfigs.clear()
}
+ originalCarrierServicePackages.forEach { (subId, pkg) ->
+ setCarrierServicePackageOverride(subId, pkg)
+ }
+ originalCarrierServicePackages.clear()
}
}
@@ -145,7 +336,7 @@
subId: Int,
keys: Set<String>
): PersistableBundle {
- return if (isAtLeastU()) {
+ return if (SdkLevel.isAtLeastU()) {
// This method is U+
getConfigForSubId(subId, *keys.toTypedArray())
} else {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
index 8a255c6..60285a8 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
@@ -45,7 +45,6 @@
import java.io.ByteArrayOutputStream
import java.io.CharArrayWriter
import java.io.File
-import java.io.FileOutputStream
import java.io.FileReader
import java.io.OutputStream
import java.io.OutputStreamWriter
@@ -280,7 +279,7 @@
}
val outFile = File(collectorDir, filename + FILENAME_SUFFIX)
outputFiles.add(filename)
- FileOutputStream(outFile).use { fos ->
+ getOutputStreamViaShell(outFile).use { fos ->
failureHeader?.let {
fos.write(it.toByteArray())
fos.write("\n".toByteArray())
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt
index bfbbc34..0413ed4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt
@@ -17,6 +17,7 @@
package com.android.testutils
import android.content.Context
+import android.net.ConnectivityManager.FEATURE_QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER
import android.net.InetAddresses.parseNumericAddress
import android.net.KeepalivePacketData
import android.net.LinkAddress
@@ -28,6 +29,7 @@
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkProvider
import android.net.NetworkRequest
+import android.net.NetworkScore
import android.net.QosFilter
import android.net.Uri
import android.os.Looper
@@ -64,16 +66,21 @@
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
+import kotlin.test.fail
import org.junit.Assert.assertArrayEquals
// Any legal score (0~99) for the test network would do, as it is going to be kept up by the
// requests filed by the test and should never match normal internet requests. 70 is the default
// score of Ethernet networks, it's as good a value as any other.
-private const val TEST_NETWORK_SCORE = 70
+private val TEST_NETWORK_SCORE = NetworkScore.Builder().setLegacyInt(70).build()
private class Provider(context: Context, looper: Looper) :
NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider")
+private val enabledFeatures = mutableMapOf(
+ FEATURE_QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER to true
+)
+
public open class TestableNetworkAgent(
context: Context,
looper: Looper,
@@ -81,8 +88,17 @@
val lp: LinkProperties,
conf: NetworkAgentConfig
) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */,
- nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) {
+ nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) {
+
+ override fun isFeatureEnabled(context: Context, feature: Long): Boolean {
+ when (val it = enabledFeatures.get(feature)) {
+ null -> fail("Unmocked feature $feature, see TestableNetworkAgent.enabledFeatures")
+ else -> return it
+ }
+ }
+
companion object {
+ fun setFeatureEnabled(flag: Long, enabled: Boolean) = enabledFeatures.set(flag, enabled)
/**
* Convenience method to create a [NetworkRequest] matching [TestableNetworkAgent]s from
diff --git a/staticlibs/testutils/host/python/apf_test_base.py b/staticlibs/testutils/host/python/apf_test_base.py
index 2552aa3..33b3838 100644
--- a/staticlibs/testutils/host/python/apf_test_base.py
+++ b/staticlibs/testutils/host/python/apf_test_base.py
@@ -60,10 +60,10 @@
self.client_ipv4_addresses = apf_utils.get_ipv4_addresses(
self.clientDevice, self.client_iface_name
)
- self.server_ipv6_addresses = apf_utils.get_ipv6_addresses(
+ self.server_ipv6_addresses = apf_utils.get_non_tentative_ipv6_addresses(
self.serverDevice, self.server_iface_name
)
- self.client_ipv6_addresses = apf_utils.get_ipv6_addresses(
+ self.client_ipv6_addresses = apf_utils.get_non_tentative_ipv6_addresses(
self.clientDevice, self.client_iface_name
)
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index c2ad18e..1648d36 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -116,12 +116,12 @@
else:
return []
-def get_ipv6_addresses(
+def get_non_tentative_ipv6_addresses(
ad: android_device.AndroidDevice, iface_name: str
) -> list[str]:
- """Retrieves the IPv6 addresses of a given interface on an Android device.
+ """Retrieves the non-tentative IPv6 addresses of a given interface on an Android device.
- This function executes an ADB shell command (`ip -6 address show`) to get the
+ This function executes an ADB shell command (`ip -6 address show -tentative`) to get the
network interface information and extracts the IPv6 address from the output.
If devices have no IPv6 address, raise PatternNotFoundException.
@@ -139,7 +139,7 @@
# valid_lft forever preferred_lft forever
# inet6 fe80::1233:aadb:3d32:1234/64 scope link
# valid_lft forever preferred_lft forever
- output = adb_utils.adb_shell(ad, f"ip -6 address show {iface_name}")
+ output = adb_utils.adb_shell(ad, f"ip -6 address show -tentative {iface_name}")
pattern = r"inet6\s+([0-9a-fA-F:]+)\/\d+"
matches = re.findall(pattern, output)
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java
index a1cf968..65844a3 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.net.TetheringInterface;
@@ -31,6 +32,8 @@
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiSsid;
+import com.android.modules.utils.build.SdkLevel;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -64,6 +67,8 @@
*/
@Test
public void testSoftApConfigurationRedactedForOtherUids() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastB());
+
mTetheringEventCallback.assumeWifiTetheringSupported(
getInstrumentation().getTargetContext());
SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
diff --git a/tests/cts/multidevices/apfv6_test.py b/tests/cts/multidevices/apfv6_test.py
index 61f1bfc..fb45f4a 100644
--- a/tests/cts/multidevices/apfv6_test.py
+++ b/tests/cts/multidevices/apfv6_test.py
@@ -13,8 +13,10 @@
# limitations under the License.
from mobly import asserts
-from scapy.layers.inet import IP, ICMP
+from scapy.layers.inet import IP, ICMP, IPOption_Router_Alert
+from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply
from scapy.layers.l2 import Ether
+from scapy.contrib.igmpv3 import IGMPv3, IGMPv3mq, IGMPv3mr, IGMPv3gr
from net_tests_utils.host.python import apf_test_base, apf_utils, adb_utils, assert_utils, packet_utils
APFV6_VERSION = 6000
@@ -98,4 +100,64 @@
expected_echo_reply = bytes(eth/ip/icmp/b"hello").hex()
self.send_packet_and_expect_reply_received(
echo_request, "DROPPED_IPV4_PING_REQUEST_REPLIED", expected_echo_reply
- )
\ No newline at end of file
+ )
+
+ @apf_utils.at_least_B()
+ def test_ipv6_icmp_echo_request_offload(self):
+ eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
+ ip = IPv6(src=self.server_ipv6_addresses[0], dst=self.client_ipv6_addresses[0])
+ icmp = ICMPv6EchoRequest(id=1, seq=123)
+ echo_request = bytes(eth/ip/icmp/b"hello").hex()
+
+ eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+ ip = IPv6(src=self.client_ipv6_addresses[0], dst=self.server_ipv6_addresses[0])
+ icmp = ICMPv6EchoReply(id=1, seq=123)
+ expected_echo_reply = bytes(eth/ip/icmp/b"hello").hex()
+
+ self.send_packet_and_expect_reply_received(
+ echo_request, "DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED", expected_echo_reply
+ )
+
+ @apf_utils.at_least_B()
+ def test_igmpv3_general_query_offload(self):
+ ether = Ether(src=self.server_mac_address, dst='01:00:5e:00:00:01')
+ ip = IP(
+ src=self.server_ipv4_addresses[0],
+ dst='224.0.0.1',
+ options=[IPOption_Router_Alert()]
+ )
+ igmp = IGMPv3(type=0x11)/IGMPv3mq()
+ igmpv3_general_query = bytes(ether/ip/igmp).hex()
+
+ mcast_addrs = ['239.0.0.1', '239.0.0.2', '239.0.0.3']
+
+ for addr in mcast_addrs:
+ adb_utils.adb_shell(
+ self.clientDevice,
+ f'ip addr add {addr}/32 dev {self.client_iface_name} autojoin'
+ )
+
+ ether = Ether(src=self.client_mac_address, dst='01:00:5e:00:00:16')
+ ip = IP(
+ src=self.client_ipv4_addresses[0],
+ dst='224.0.0.22',
+ options=[IPOption_Router_Alert()],
+ id=0,
+ flags="DF"
+ )
+ igmpv3_hdr = IGMPv3(type=0x22)
+ mcast_records = []
+ for addr in mcast_addrs:
+ mcast_records.append(IGMPv3gr(rtype=2, maddr=addr))
+
+ igmp = IGMPv3mr(records=mcast_records)
+ expected_igmpv3_report = bytes(ether/ip/igmpv3_hdr/igmp).hex()
+ self.send_packet_and_expect_reply_received(
+ igmpv3_general_query, "DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED", expected_igmpv3_report
+ )
+
+ for addr in mcast_addrs:
+ adb_utils.adb_shell(
+ self.clientDevice,
+ f'ip addr del {addr}/32 dev {self.client_iface_name}'
+ )
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 81afabc..7dbb9b2 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -372,7 +372,7 @@
if (caps.apfVersionSupported > 4) {
assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
- assertThat(caps.apfVersionSupported).isEqualTo(6000) // v6.0000
+ assertThat(caps.apfVersionSupported).isAnyOf(6000, 6100) // v6.000 or v6.100
}
// DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set
@@ -383,14 +383,22 @@
assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
}
- // CHIPSETs (or DEVICES with CHIPSETs) that set ro.board.first_api_level or
- // ro.board.api_level to 202504 or higher:
- // - [VSR-5.3.12-018] MUST implement version 6 of the Android Packet Filtering (APF)
- // interpreter in the Wi-Fi firmware.
- // - [VSR-5.3.12-019] MUST provide at least 4000 bytes of APF RAM.
+ // DEVICEs with CHIPSETs that set ro.board.first_api_level or ro.board.api_level to 202504
+ // or higher:
+ // - [VSR-5.3.12-018] MUST implement version 6 or version 6.1 of the Android Packet
+ // Filtering (APF) interpreter in the Wi-Fi firmware.
+ // - [VSR-5.3.12-019] MUST provide at least 4000 bytes of APF RAM when version 6 is
+ // implemented OR 3000 bytes when version 6.1 is implemented.
+ // - Note, the APF RAM requirement for APF version 6.1 will become 4000 bytes in Android 17
+ // with CHIPSETs that set ro.board.first_api_level or ro.board.api_level to 202604 or
+ // higher.
if (vsrApiLevel >= 202504) {
- assertThat(caps.apfVersionSupported).isEqualTo(6000)
- assertThat(caps.maximumApfProgramSize).isAtLeast(4000)
+ assertThat(caps.apfVersionSupported).isAnyOf(6000, 6100)
+ if (caps.apfVersionSupported == 6000) {
+ assertThat(caps.maximumApfProgramSize).isAtLeast(4000)
+ } else {
+ assertThat(caps.maximumApfProgramSize).isAtLeast(3000)
+ }
}
// ApfFilter does not support anything but ARPHRD_ETHER.
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 5e035a2..bd9bd2a 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -15,7 +15,6 @@
*/
package android.net.cts
-import android.Manifest.permission.MODIFY_PHONE_STATE
import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
import android.app.Instrumentation
@@ -80,12 +79,10 @@
import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost
import android.net.wifi.WifiInfo
import android.os.Build
-import android.os.ConditionVariable
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Message
-import android.os.PersistableBundle
import android.os.Process
import android.os.SystemClock
import android.platform.test.annotations.AppModeFull
@@ -94,19 +91,15 @@
import android.system.OsConstants.IPPROTO_TCP
import android.system.OsConstants.IPPROTO_UDP
import android.system.OsConstants.SOCK_DGRAM
-import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.CarrierPrivilegesCallback
import android.telephony.data.EpsBearerQosSessionAttributes
import android.util.ArraySet
import android.util.DebugUtils.valueToString
-import android.util.Log
import androidx.test.InstrumentationRegistry
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.ThrowingSupplier
-import com.android.compatibility.common.util.UiccUtil
import com.android.modules.utils.build.SdkLevel
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.NetworkStackConstants.ETHER_MTU
@@ -151,13 +144,12 @@
import java.net.InetSocketAddress
import java.net.Socket
import java.nio.ByteBuffer
-import java.security.MessageDigest
import java.time.Duration
import java.util.Arrays
-import java.util.Random
import java.util.UUID
import java.util.concurrent.Executors
import kotlin.collections.ArrayList
+import kotlin.random.Random
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
@@ -267,7 +259,7 @@
private class FakeConnectivityService {
val mockRegistry = mock(INetworkAgentRegistry::class.java)
private var agentField: INetworkAgent? = null
- private val registry = object : INetworkAgentRegistry.Stub(),
+ val registry: INetworkAgentRegistry = object : INetworkAgentRegistry.Stub(),
INetworkAgentRegistry by mockRegistry {
// asBinder has implementations in both INetworkAgentRegistry.Stub and mockRegistry, so
// it needs to be disambiguated. Just fail the test as it should be unused here.
@@ -284,7 +276,7 @@
fun connect(agent: INetworkAgent) {
this.agentField = agent
- agent.onRegistered(registry)
+ agent.onRegistered()
}
fun disconnect() = agent.onDisconnected()
@@ -392,9 +384,9 @@
initialLp = lp,
initialNc = nc
)
- agent.setTeardownDelayMillis(0)
// Connect the agent and verify initial status callbacks.
agent.register()
+ agent.setTeardownDelayMillis(0)
agent.markConnected()
agent.expectCallback<OnNetworkCreated>()
agent.expectPostConnectionCallbacks(expectedInitSignalStrengthThresholds)
@@ -413,7 +405,8 @@
}
private fun createNetworkAgentWithFakeCS() = createNetworkAgent().also {
- mFakeConnectivityService.connect(it.registerForTest(Network(FAKE_NET_ID)))
+ val binder = it.registerForTest(Network(FAKE_NET_ID), mFakeConnectivityService.registry)
+ mFakeConnectivityService.connect(binder)
}
private fun TestableNetworkAgent.expectPostConnectionCallbacks(
@@ -708,102 +701,6 @@
doTestAllowedUids(transports, uid, expectUidsPresent, specifier, transportInfo)
}
- private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
- fun getCertHash(): String {
- val pkgInfo = realContext.packageManager.getPackageInfo(
- realContext.opPackageName,
- PackageManager.GET_SIGNATURES
- )
- val digest = MessageDigest.getInstance("SHA-256")
- val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray())
- return UiccUtil.bytesToHexString(certHash)!!
- }
-
- val tm = realContext.getSystemService(TelephonyManager::class.java)!!
-
- val cv = ConditionVariable()
- val cpb = PrivilegeWaiterCallback(cv)
- // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
- // T. This means the lambda will compile as a private method of this class taking a
- // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
- // including private methods, this would fail with a link error when running on S-.
- // To solve this, make the lambda serializable, which causes the compiler to emit a
- // synthetic class instead of a synthetic method.
- tryTest @JvmSerializableLambda {
- val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
- runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
- tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
- }
- // Wait for the callback to be registered
- assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
- if (cpb.hasPrivilege == hold) {
- if (hold) {
- Log.w(TAG, "Package ${realContext.opPackageName} already is privileged")
- } else {
- Log.w(TAG, "Package ${realContext.opPackageName} already isn't privileged")
- }
- return@tryTest
- }
- if (hold) {
- carrierConfigRule.addConfigOverrides(subId, PersistableBundle().also {
- it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
- arrayOf(getCertHash()))
- })
- } else {
- carrierConfigRule.cleanUpNow()
- }
- } cleanup @JvmSerializableLambda {
- runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
- tm.unregisterCarrierPrivilegesCallback(cpb)
- }
- }
- }
-
- private fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId)
- private fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId)
-
- private fun setCarrierServicePackageOverride(subId: Int, pkg: String?) {
- val tm = realContext.getSystemService(TelephonyManager::class.java)!!
-
- val cv = ConditionVariable()
- val cpb = CarrierServiceChangedWaiterCallback(cv)
- // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
- // T. This means the lambda will compile as a private method of this class taking a
- // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
- // including private methods, this would fail with a link error when running on S-.
- // To solve this, make the lambda serializable, which causes the compiler to emit a
- // synthetic class instead of a synthetic method.
- tryTest @JvmSerializableLambda {
- val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
- runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
- tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
- }
- // Wait for the callback to be registered
- assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
- if (cpb.pkgName == pkg) {
- Log.w(TAG, "Carrier service package was already $pkg")
- return@tryTest
- }
- cv.close()
- runAsShell(MODIFY_PHONE_STATE) {
- if (null == pkg) {
- // There is a bug is clear-carrier-service-package-override where not adding
- // the -s argument will use the wrong slot index : b/299604822
- runShellCommand("cmd phone clear-carrier-service-package-override" +
- " -s $subId")
- } else {
- // -s could set the subId, but this test works with the default subId.
- runShellCommand("cmd phone set-carrier-service-package-override $pkg")
- }
- }
- assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't modify carrier service package")
- } cleanup @JvmSerializableLambda {
- runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
- tm.unregisterCarrierPrivilegesCallback(cpb)
- }
- }
- }
-
private fun String.execute() = runShellCommand(this).trim()
@Test
@@ -856,8 +753,8 @@
if (!SdkLevel.isAtLeastU()) return@tryTest
// Acquiring carrier privilege is necessary to override the carrier service package.
val defaultSlotIndex = SubscriptionManager.getSlotIndex(defaultSubId)
- acquireCarrierPrivilege(defaultSubId)
- setCarrierServicePackageOverride(defaultSubId, servicePackage)
+ carrierConfigRule.acquireCarrierPrivilege(defaultSubId)
+ carrierConfigRule.setCarrierServicePackageOverride(defaultSubId, servicePackage)
val actualServicePackage: String? = runAsShell(READ_PRIVILEGED_PHONE_STATE) {
tm.getCarrierServicePackageNameForLogicalSlot(defaultSlotIndex)
}
@@ -896,10 +793,6 @@
expectUidsPresent = false)
doTestAllowedUidsWithSubId(defaultSubId, intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI),
uid, expectUidsPresent = false)
- } cleanupStep {
- if (SdkLevel.isAtLeastU()) setCarrierServicePackageOverride(defaultSubId, null)
- } cleanup {
- if (SdkLevel.isAtLeastU()) dropCarrierPrivilege(defaultSubId)
}
}
@@ -1056,6 +949,47 @@
callback.expect<Lost>(agent.network!!)
}
+ fun doTestOemVpnType(type: Int) {
+ val mySessionId = "MySession12345"
+ val nc = NetworkCapabilities().apply {
+ addTransportType(TRANSPORT_TEST)
+ addTransportType(TRANSPORT_VPN)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ removeCapability(NET_CAPABILITY_NOT_VPN)
+ setTransportInfo(VpnTransportInfo(type, mySessionId))
+ }
+
+ val agent = createNetworkAgent(initialNc = nc)
+ agent.register()
+ agent.markConnected()
+
+ val request = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_VPN)
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .build()
+ val callback = TestableNetworkCallback()
+ registerNetworkCallback(request, callback)
+
+ callback.expectAvailableThenValidatedCallbacks(agent.network!!)
+
+ var vpnNc = mCM.getNetworkCapabilities(agent.network!!)
+ assertNotNull(vpnNc)
+ assertEquals(type, (vpnNc!!.transportInfo as VpnTransportInfo).type)
+
+ agent.unregister()
+ callback.expect<Lost>(agent.network!!)
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun testOemVpnTypes() {
+ // TODO: why is this necessary given the @IgnoreUpTo above?
+ assumeTrue(SdkLevel.isAtLeastB())
+ doTestOemVpnType(VpnManager.TYPE_VPN_OEM_SERVICE)
+ doTestOemVpnType(VpnManager.TYPE_VPN_OEM_LEGACY)
+ }
+
private fun unregister(agent: TestableNetworkAgent) {
agent.unregister()
agent.eventuallyExpect<OnNetworkUnwanted>()
@@ -1628,7 +1562,7 @@
val s = Os.socket(AF_INET6, SOCK_DGRAM, 0)
net.bindSocket(s)
val content = ByteArray(16)
- Random().nextBytes(content)
+ Random.nextBytes(content)
Os.sendto(s, ByteBuffer.wrap(content), 0, REMOTE_ADDRESS, 7 /* port */)
val match = reader.poll(DEFAULT_TIMEOUT_MS) {
val udpStart = IPV6_HEADER_LEN + UDP_HEADER_LEN
@@ -2000,26 +1934,19 @@
// VPN networks are always created as soon as the agent is registered.
doTestNativeNetworkCreation(expectCreatedImmediately = true, intArrayOf(TRANSPORT_VPN))
}
-}
-// Subclasses of CarrierPrivilegesCallback can't be inline, or they'll be compiled as
-// inner classes of the test class and will fail resolution on R as the test harness
-// uses reflection to list all methods and classes
-class PrivilegeWaiterCallback(private val cv: ConditionVariable) :
- CarrierPrivilegesCallback {
- var hasPrivilege = false
- override fun onCarrierPrivilegesChanged(p: MutableSet<String>, uids: MutableSet<Int>) {
- hasPrivilege = uids.contains(Process.myUid())
- cv.open()
+ @Test(expected = IllegalStateException::class)
+ fun testRegisterAgain() {
+ val agent = createNetworkAgent()
+ agent.register()
+ agent.unregister()
+ agent.register()
}
-}
-class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) :
- CarrierPrivilegesCallback {
- var pkgName: String? = null
- override fun onCarrierPrivilegesChanged(p: MutableSet<String>, u: MutableSet<Int>) {}
- override fun onCarrierServiceChanged(pkgName: String?, uid: Int) {
- this.pkgName = pkgName
- cv.open()
+ @Test
+ fun testUnregisterBeforeRegister() {
+ // For backward compatibility, this shouldn't crash.
+ val agent = createNetworkAgent()
+ agent.unregister()
}
}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
index 27cba3a..3b8f5bc 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -119,7 +119,10 @@
cv instanceof CallbackValue.OnTetheringStarted);
}
- public void expectTetheringFailed(final int expected) throws InterruptedException {
+ /**
+ * Verify that starting tethering failed with the specified error code.
+ */
+ public void expectTetheringFailed(final int expected) {
final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true);
assertNotNull("No onTetheringFailed after " + TIMEOUT_MS + " ms", cv);
assertTrue("Expect fail with error code " + expected + ", but received: " + cv,
@@ -559,19 +562,28 @@
}
/**
- * Starts Wi-Fi tethering.
+ * Starts Wi-Fi tethering with TETHER_PRIVILEGED permission.
*/
- public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback)
- throws InterruptedException {
+ public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback) {
return startWifiTethering(callback, null);
}
/**
- * Starts Wi-Fi tethering with the specified SoftApConfiguration.
+ * Starts Wi-Fi tethering with TETHER_PRIVILEGED permission and the specified
+ * SoftApConfiguration.
*/
public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback,
- final SoftApConfiguration softApConfiguration)
- throws InterruptedException {
+ final SoftApConfiguration softApConfiguration) {
+ return runAsShell(TETHER_PRIVILEGED, () -> startWifiTetheringNoPermissions(
+ callback, softApConfiguration));
+ }
+
+ /**
+ * Starts Wi-Fi tethering without any permission with the specified SoftApConfiguration.
+ */
+ public TetheringInterface startWifiTetheringNoPermissions(
+ final TestTetheringEventCallback callback,
+ final SoftApConfiguration softApConfiguration) {
final List<String> wifiRegexs = getWifiTetherableInterfaceRegexps(callback);
final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
@@ -582,19 +594,17 @@
}
final TetheringRequest request = builder.build();
- return runAsShell(TETHER_PRIVILEGED, () -> {
- mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
- startTetheringCallback.verifyTetheringStarted();
+ mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
+ startTetheringCallback.verifyTetheringStarted();
- final TetheringInterface iface =
- callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI);
+ final TetheringInterface iface =
+ callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI);
- callback.expectOneOfOffloadStatusChanged(
- TETHER_HARDWARE_OFFLOAD_STARTED,
- TETHER_HARDWARE_OFFLOAD_FAILED);
+ callback.expectOneOfOffloadStatusChanged(
+ TETHER_HARDWARE_OFFLOAD_STARTED,
+ TETHER_HARDWARE_OFFLOAD_FAILED);
- return iface;
- });
+ return iface;
}
private static class StopSoftApCallback implements SoftApCallback {
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 7d6a213..abe628b 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -487,6 +487,9 @@
final TestTetheringEventCallback tetherEventCallback =
mCtsTetheringUtils.registerTetheringEventCallback();
try {
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ tetherEventCallback.expectNoTetheringActive();
+
final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
mTM.startTethering(new TetheringRequest.Builder(TETHERING_VIRTUAL).build(),
c -> c.run(), startTetheringCallback);
@@ -508,6 +511,7 @@
mCtsTetheringUtils.registerTetheringEventCallback();
try {
tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ tetherEventCallback.expectNoTetheringActive();
SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
.setWifiSsid(WifiSsid.fromBytes("This is one config"
@@ -532,6 +536,7 @@
mCtsTetheringUtils.registerTetheringEventCallback();
try {
tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ tetherEventCallback.expectNoTetheringActive();
SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
.setWifiSsid(WifiSsid.fromBytes("This is one config"
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index c28a0f8..3eefa0f 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -2200,6 +2200,7 @@
case ConnectivityFlags.DELAY_DESTROY_SOCKETS:
case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS:
case ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS:
+ case ConnectivityFlags.QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER:
return true;
default:
throw new UnsupportedOperationException("Unknown flag " + name
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
index 94c68c0..4aeae19 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
@@ -45,20 +45,29 @@
.addTransportType(TRANSPORT_WIFI)
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
.build()
- val keepConnectedAgent = Agent(nc = nc, score = FromS(NetworkScore.Builder()
- .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
- .build()),
- lnc = FromS(LocalNetworkConfig.Builder().build()))
- val dontKeepConnectedAgent = Agent(nc = nc,
- lnc = FromS(LocalNetworkConfig.Builder().build()))
+ val keepConnectedAgent = Agent(
+ nc = nc,
+ score = FromS(
+ NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build()
+ ),
+ lnc = FromS(LocalNetworkConfig.Builder().build())
+ )
+ val dontKeepConnectedAgent = Agent(
+ nc = nc,
+ lnc = FromS(LocalNetworkConfig.Builder().build())
+ )
doTestKeepConnected(keepConnectedAgent, dontKeepConnectedAgent)
}
@Test
fun testKeepConnectedForTest() {
- val keepAgent = Agent(score = FromS(NetworkScore.Builder()
- .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
- .build()))
+ val keepAgent = Agent(score = FromS(
+ NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
+ .build()
+ ))
val dontKeepAgent = Agent()
doTestKeepConnected(keepAgent, dontKeepAgent)
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt
index 16a30aa..6805d9a 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt
@@ -61,7 +61,8 @@
data class TestParams(
val sdkLevel: Int,
val isTv: Boolean = false,
- val addLocalNetCapToRequest: Boolean = true)
+ val addLocalNetCapToRequest: Boolean = true
+ )
companion object {
@JvmStatic
@@ -81,8 +82,14 @@
}
private fun makeNativeNetworkConfigLocal(netId: Int, permission: Int) =
- NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL_LOCAL, permission,
- false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
+ NativeNetworkConfig(
+ netId,
+ NativeNetworkType.PHYSICAL_LOCAL,
+ permission,
+ false /* secure */,
+ VpnManager.TYPE_VPN_NONE,
+ false /* excludeLocalRoutes */
+ )
@Test
fun testLocalAgents() {
@@ -99,8 +106,8 @@
addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
addCapability(NET_CAPABILITY_LOCAL_NETWORK)
}.build()
- val localAgent = if (params.sdkLevel >= VERSION_V
- || params.sdkLevel == VERSION_U && params.isTv) {
+ val localAgent = if (params.sdkLevel >= VERSION_V ||
+ params.sdkLevel == VERSION_U && params.isTv) {
Agent(nc = ncTemplate, score = keepConnectedScore(), lnc = defaultLnc())
} else {
assertFailsWith<IllegalArgumentException> { Agent(nc = ncTemplate, lnc = defaultLnc()) }
@@ -109,7 +116,8 @@
}
localAgent.connect()
netdInOrder.verify(netd).networkCreate(
- makeNativeNetworkConfigLocal(localAgent.network.netId, INetd.PERMISSION_NONE))
+ makeNativeNetworkConfigLocal(localAgent.network.netId, INetd.PERMISSION_NONE)
+ )
if (params.addLocalNetCapToRequest) {
assertEquals(localAgent.network, allNetworksCb.expect<Available>().network)
} else {
@@ -123,10 +131,12 @@
@Test
fun testBadAgents() {
assertFailsWith<IllegalArgumentException> {
- Agent(nc = NetworkCapabilities.Builder()
- .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build(),
- lnc = null)
+ Agent(
+ nc = NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ lnc = null
+ )
}
assertFailsWith<IllegalArgumentException> {
Agent(nc = NetworkCapabilities.Builder().build(), lnc = defaultLnc())
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
index 3583f84..d835155 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
@@ -111,14 +111,18 @@
deps.setBuildSdk(VERSION_V)
assertFailsWith<IllegalArgumentException> {
- Agent(nc = NetworkCapabilities.Builder()
- .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build(),
- lnc = null)
+ Agent(
+ nc = NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ lnc = null
+ )
}
assertFailsWith<IllegalArgumentException> {
- Agent(nc = NetworkCapabilities.Builder().build(),
- lnc = FromS(LocalNetworkConfig.Builder().build()))
+ Agent(
+ nc = NetworkCapabilities.Builder().build(),
+ lnc = FromS(LocalNetworkConfig.Builder().build())
+ )
}
}
@@ -127,27 +131,31 @@
deps.setBuildSdk(VERSION_V)
val cb = TestableNetworkCallback()
- cm.requestNetwork(NetworkRequest.Builder()
- .clearCapabilities()
- .build(),
- cb)
- val agent = Agent(nc = NetworkCapabilities.Builder()
- .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build(),
- lnc = FromS(LocalNetworkConfig.Builder().build()))
+ cm.requestNetwork(
+ NetworkRequest.Builder()
+ .clearCapabilities()
+ .build(),
+ cb
+ )
+ val agent = Agent(
+ nc = NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ lnc = FromS(LocalNetworkConfig.Builder().build())
+ )
agent.connect()
cb.expectAvailableCallbacks(agent.network, validated = false)
agent.sendNetworkCapabilities(NetworkCapabilities.Builder().build())
cb.expect<Lost>(agent.network)
- val agent2 = Agent(nc = NetworkCapabilities.Builder()
- .build(),
- lnc = null)
+ val agent2 = Agent(nc = NetworkCapabilities.Builder().build(), lnc = null)
agent2.connect()
cb.expectAvailableCallbacks(agent2.network, validated = false)
- agent2.sendNetworkCapabilities(NetworkCapabilities.Builder()
- .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build())
+ agent2.sendNetworkCapabilities(
+ NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build()
+ )
cb.expect<Lost>(agent2.network)
}
@@ -156,10 +164,12 @@
deps.setBuildSdk(VERSION_V)
val cb = TestableNetworkCallback()
- cm.requestNetwork(NetworkRequest.Builder()
- .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build(),
- cb)
+ cm.requestNetwork(
+ NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ cb
+ )
// Set up a local agent that should forward its traffic to the best DUN upstream.
val localAgent = Agent(
@@ -171,15 +181,20 @@
cb.expectAvailableCallbacks(localAgent.network, validated = false)
- val wifiAgent = Agent(score = keepScore(), lp = lp("wifi0"),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ val wifiAgent = Agent(
+ score = keepScore(),
+ lp = lp("wifi0"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+ )
wifiAgent.connect()
val newLnc = LocalNetworkConfig.Builder()
- .setUpstreamSelector(NetworkRequest.Builder()
- .addTransportType(TRANSPORT_WIFI)
- .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build())
+ .setUpstreamSelector(
+ NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build()
+ )
.build()
localAgent.sendLocalNetworkConfig(newLnc)
@@ -207,21 +222,29 @@
nc = nc(TRANSPORT_THREAD, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp(name),
lnc = localNetworkConfig,
- score = FromS(NetworkScore.Builder()
+ score = FromS(
+ NetworkScore.Builder()
.setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
- .build())
+ .build()
+ )
)
return localAgent
}
private fun createWifiAgent(name: String): CSAgentWrapper {
- return Agent(score = keepScore(), lp = lp(name),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ return Agent(
+ score = keepScore(),
+ lp = lp(name),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+ )
}
private fun createCellAgent(name: String): CSAgentWrapper {
- return Agent(score = keepScore(), lp = lp(name),
- nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET))
+ return Agent(
+ score = keepScore(),
+ lp = lp(name),
+ nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET)
+ )
}
private fun sendLocalNetworkConfig(
@@ -245,11 +268,12 @@
cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
val inOrder = inOrder(multicastRoutingCoordinatorService)
- val lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(upstreamSelectorWifi)
- .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
- .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
- .build()
+ val lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(upstreamSelectorWifi)
+ .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+ .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+ .build()
)
val localAgent = createLocalAgent("local0", lnc)
localAgent.connect()
@@ -264,9 +288,15 @@
}
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "local0", "wifi0", multicastRoutingConfigMinScope)
+ "local0",
+ "wifi0",
+ multicastRoutingConfigMinScope
+ )
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "wifi0", "local0", multicastRoutingConfigSelected)
+ "wifi0",
+ "local0",
+ multicastRoutingConfigSelected
+ )
wifiAgent.disconnect()
@@ -282,11 +312,12 @@
fun testMulticastRoutingConfig_2LocalNetworks() {
deps.setBuildSdk(VERSION_V)
val inOrder = inOrder(multicastRoutingCoordinatorService)
- val lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(upstreamSelectorWifi)
- .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
- .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
- .build()
+ val lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(upstreamSelectorWifi)
+ .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+ .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+ .build()
)
val localAgent0 = createLocalAgent("local0", lnc)
localAgent0.connect()
@@ -296,18 +327,30 @@
waitForIdle()
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "local0", "wifi0", multicastRoutingConfigMinScope)
+ "local0",
+ "wifi0",
+ multicastRoutingConfigMinScope
+ )
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "wifi0", "local0", multicastRoutingConfigSelected)
+ "wifi0",
+ "local0",
+ multicastRoutingConfigSelected
+ )
val localAgent1 = createLocalAgent("local1", lnc)
localAgent1.connect()
waitForIdle()
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "local1", "wifi0", multicastRoutingConfigMinScope)
+ "local1",
+ "wifi0",
+ multicastRoutingConfigMinScope
+ )
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "wifi0", "local1", multicastRoutingConfigSelected)
+ "wifi0",
+ "local1",
+ multicastRoutingConfigSelected
+ )
localAgent0.disconnect()
localAgent1.disconnect()
@@ -318,15 +361,19 @@
fun testMulticastRoutingConfig_UpstreamNetworkCellToWifi() {
deps.setBuildSdk(VERSION_V)
val cb = TestableNetworkCallback()
- cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities()
+ cm.registerNetworkCallback(
+ NetworkRequest.Builder().clearCapabilities()
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build(), cb)
+ .build(),
+ cb
+ )
val inOrder = inOrder(multicastRoutingCoordinatorService)
- val lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(upstreamSelectorAny)
- .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
- .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
- .build()
+ val lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(upstreamSelectorAny)
+ .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+ .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+ .build()
)
val localAgent = createLocalAgent("local0", lnc)
val wifiAgent = createWifiAgent("wifi0")
@@ -341,9 +388,15 @@
}
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "local0", "cell0", multicastRoutingConfigMinScope)
+ "local0",
+ "cell0",
+ multicastRoutingConfigMinScope
+ )
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "cell0", "local0", multicastRoutingConfigSelected)
+ "cell0",
+ "local0",
+ multicastRoutingConfigSelected
+ )
wifiAgent.connect()
@@ -357,9 +410,15 @@
inOrder.verify(multicastRoutingCoordinatorService)
.applyMulticastRoutingConfig("cell0", "local0", CONFIG_FORWARD_NONE)
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "local0", "wifi0", multicastRoutingConfigMinScope)
+ "local0",
+ "wifi0",
+ multicastRoutingConfigMinScope
+ )
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "wifi0", "local0", multicastRoutingConfigSelected)
+ "wifi0",
+ "local0",
+ multicastRoutingConfigSelected
+ )
localAgent.disconnect()
cellAgent.disconnect()
@@ -370,15 +429,19 @@
fun testMulticastRoutingConfig_UpstreamSelectorCellToWifi() {
deps.setBuildSdk(VERSION_V)
val cb = TestableNetworkCallback()
- cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities()
+ cm.registerNetworkCallback(
+ NetworkRequest.Builder().clearCapabilities()
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build(), cb)
+ .build(),
+ cb
+ )
val inOrder = inOrder(multicastRoutingCoordinatorService)
- val lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(upstreamSelectorCell)
- .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
- .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
- .build()
+ val lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(upstreamSelectorCell)
+ .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+ .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+ .build()
)
val localAgent = createLocalAgent("local0", lnc)
val wifiAgent = createWifiAgent("wifi0")
@@ -393,12 +456,22 @@
}
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "local0", "cell0", multicastRoutingConfigMinScope)
+ "local0",
+ "cell0",
+ multicastRoutingConfigMinScope
+ )
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "cell0", "local0", multicastRoutingConfigSelected)
+ "cell0",
+ "local0",
+ multicastRoutingConfigSelected
+ )
- sendLocalNetworkConfig(localAgent, upstreamSelectorWifi, multicastRoutingConfigMinScope,
- multicastRoutingConfigSelected)
+ sendLocalNetworkConfig(
+ localAgent,
+ upstreamSelectorWifi,
+ multicastRoutingConfigMinScope,
+ multicastRoutingConfigSelected
+ )
cb.expect<LocalInfoChanged>(localAgent.network) {
it.info.upstreamNetwork == wifiAgent.network
}
@@ -409,9 +482,15 @@
inOrder.verify(multicastRoutingCoordinatorService)
.applyMulticastRoutingConfig("cell0", "local0", CONFIG_FORWARD_NONE)
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "local0", "wifi0", multicastRoutingConfigMinScope)
+ "local0",
+ "wifi0",
+ multicastRoutingConfigMinScope
+ )
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "wifi0", "local0", multicastRoutingConfigSelected)
+ "wifi0",
+ "local0",
+ multicastRoutingConfigSelected
+ )
localAgent.disconnect()
cellAgent.disconnect()
@@ -422,15 +501,19 @@
fun testMulticastRoutingConfig_UpstreamSelectorWifiToNull() {
deps.setBuildSdk(VERSION_V)
val cb = TestableNetworkCallback()
- cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities()
+ cm.registerNetworkCallback(
+ NetworkRequest.Builder().clearCapabilities()
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build(), cb)
+ .build(),
+ cb
+ )
val inOrder = inOrder(multicastRoutingCoordinatorService)
- val lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(upstreamSelectorWifi)
- .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
- .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
- .build()
+ val lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(upstreamSelectorWifi)
+ .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+ .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+ .build()
)
val localAgent = createLocalAgent("local0", lnc)
localAgent.connect()
@@ -442,12 +525,22 @@
}
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "local0", "wifi0", multicastRoutingConfigMinScope)
+ "local0",
+ "wifi0",
+ multicastRoutingConfigMinScope
+ )
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "wifi0", "local0", multicastRoutingConfigSelected)
+ "wifi0",
+ "local0",
+ multicastRoutingConfigSelected
+ )
- sendLocalNetworkConfig(localAgent, null, multicastRoutingConfigMinScope,
- multicastRoutingConfigSelected)
+ sendLocalNetworkConfig(
+ localAgent,
+ null,
+ multicastRoutingConfigMinScope,
+ multicastRoutingConfigSelected
+ )
cb.expect<LocalInfoChanged>(localAgent.network) {
it.info.upstreamNetwork == null
}
@@ -458,9 +551,15 @@
inOrder.verify(multicastRoutingCoordinatorService)
.applyMulticastRoutingConfig("wifi0", "local0", CONFIG_FORWARD_NONE)
inOrder.verify(multicastRoutingCoordinatorService, never()).applyMulticastRoutingConfig(
- eq("local0"), any(), eq(multicastRoutingConfigMinScope))
+ eq("local0"),
+ any(),
+ eq(multicastRoutingConfigMinScope)
+ )
inOrder.verify(multicastRoutingCoordinatorService, never()).applyMulticastRoutingConfig(
- any(), eq("local0"), eq(multicastRoutingConfigSelected))
+ any(),
+ eq("local0"),
+ eq(multicastRoutingConfigSelected)
+ )
localAgent.disconnect()
wifiAgent.disconnect()
@@ -482,23 +581,30 @@
cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
// Set up a local agent that should forward its traffic to the best wifi upstream.
- val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ val localAgent = Agent(
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
- lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(upstreamSelectorWifi)
- .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
- .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
- .build()),
- score = FromS(NetworkScore.Builder()
- .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
- .build())
+ lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(upstreamSelectorWifi)
+ .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+ .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+ .build()
+ ),
+ score = FromS(
+ NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build()
+ )
)
localAgent.connect()
cb.expectAvailableCallbacks(localAgent.network, validated = false)
- val wifiAgent = Agent(lp = lp("wifi0"),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ val wifiAgent = Agent(
+ lp = lp("wifi0"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+ )
wifiAgent.connect()
cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
@@ -519,8 +625,10 @@
inOrder.verify(netd).networkDestroy(wifiAgent.network.netId)
val wifiIface2 = if (sameIfaceName) "wifi0" else "wifi1"
- val wifiAgent2 = Agent(lp = lp(wifiIface2),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ val wifiAgent2 = Agent(
+ lp = lp(wifiIface2),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+ )
wifiAgent2.connect()
cb.expectAvailableCallbacks(wifiAgent2.network, validated = false)
@@ -529,9 +637,15 @@
inOrder.verify(netd).ipfwdAddInterfaceForward("local0", wifiIface2)
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- "local0", wifiIface2, multicastRoutingConfigMinScope)
+ "local0",
+ wifiIface2,
+ multicastRoutingConfigMinScope
+ )
inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
- wifiIface2, "local0", multicastRoutingConfigSelected)
+ wifiIface2,
+ "local0",
+ multicastRoutingConfigSelected
+ )
inOrder.verify(netd, never()).ipfwdRemoveInterfaceForward(any(), any())
inOrder.verify(multicastRoutingCoordinatorService, never())
@@ -547,24 +661,35 @@
cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
// Set up a local agent that should forward its traffic to the best wifi upstream.
- val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ val localAgent = Agent(
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
- lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(NetworkRequest.Builder()
- .addTransportType(TRANSPORT_WIFI)
- .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build())
- .build()),
- score = FromS(NetworkScore.Builder()
- .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
- .build())
+ lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(
+ NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addForbiddenCapability(
+ NET_CAPABILITY_LOCAL_NETWORK
+ )
+ .build()
+ )
+ .build()
+ ),
+ score = FromS(
+ NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build()
+ )
)
localAgent.connect()
cb.expectAvailableCallbacks(localAgent.network, validated = false)
- val wifiAgent = Agent(lp = lp("wifi0"),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ val wifiAgent = Agent(
+ lp = lp("wifi0"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+ )
wifiAgent.connect()
cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
@@ -587,21 +712,27 @@
deps.setBuildSdk(VERSION_V)
val localCb = TestableNetworkCallback()
- cm.requestNetwork(NetworkRequest.Builder().clearCapabilities()
- .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build(),
- localCb)
+ cm.requestNetwork(
+ NetworkRequest.Builder().clearCapabilities()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ localCb
+ )
val cb = TestableNetworkCallback()
cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
val localNc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK)
- val lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(NetworkRequest.Builder()
- .addTransportType(TRANSPORT_WIFI)
- .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build())
- .build())
+ val lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(
+ NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build()
+ )
+ .build()
+ )
val localScore = FromS(NetworkScore.Builder().build())
// Set up a local agent that should forward its traffic to the best wifi upstream.
@@ -628,10 +759,16 @@
val localAgent2 = Agent(nc = localNc, lp = lp("local0"), lnc = lnc, score = localScore)
localAgent2.connect()
- localCb.expectAvailableCallbacks(localAgent2.network,
- validated = false, upstream = wifiAgent.network)
- cb.expectAvailableCallbacks(localAgent2.network,
- validated = false, upstream = wifiAgent.network)
+ localCb.expectAvailableCallbacks(
+ localAgent2.network,
+ validated = false,
+ upstream = wifiAgent.network
+ )
+ cb.expectAvailableCallbacks(
+ localAgent2.network,
+ validated = false,
+ upstream = wifiAgent.network
+ )
cb.expect<Lost> { it.network == localAgent.network }
}
@@ -648,24 +785,36 @@
// Unregister wifi pending replacement, then set up a local agent that would have
// this network as its upstream.
wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
- val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ val localAgent = Agent(
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
- lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(NetworkRequest.Builder()
- .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .addTransportType(TRANSPORT_WIFI)
- .build())
- .build()),
- score = FromS(NetworkScore.Builder()
- .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
- .build())
+ lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(
+ NetworkRequest.Builder()
+ .addForbiddenCapability(
+ NET_CAPABILITY_LOCAL_NETWORK
+ )
+ .addTransportType(TRANSPORT_WIFI)
+ .build()
+ )
+ .build()
+ ),
+ score = FromS(
+ NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build()
+ )
)
// Connect the local agent. The zombie wifi is its upstream, but the stack doesn't
// tell netd to add the forward since the wifi0 interface has gone.
localAgent.connect()
- cb.expectAvailableCallbacks(localAgent.network,
- validated = false, upstream = wifiAgent.network)
+ cb.expectAvailableCallbacks(
+ localAgent.network,
+ validated = false,
+ upstream = wifiAgent.network
+ )
verify(netd, never()).ipfwdAddInterfaceForward("local0", "wifi0")
@@ -679,33 +828,51 @@
fun testForwardingRules() {
deps.setBuildSdk(VERSION_V)
// Set up a local agent that should forward its traffic to the best DUN upstream.
- val lnc = FromS(LocalNetworkConfig.Builder()
- .setUpstreamSelector(NetworkRequest.Builder()
- .addCapability(NET_CAPABILITY_DUN)
- .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build())
- .build())
- val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ val lnc = FromS(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(
+ NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_DUN)
+ .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build()
+ )
+ .build()
+ )
+ val localAgent = Agent(
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
lnc = lnc,
- score = FromS(NetworkScore.Builder()
- .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
- .build())
+ score = FromS(
+ NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build()
+ )
)
localAgent.connect()
- val wifiAgent = Agent(score = keepScore(), lp = lp("wifi0"),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
- val cellAgentDun = Agent(score = keepScore(), lp = lp("cell0"),
- nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
- val wifiAgentDun = Agent(score = keepScore(), lp = lp("wifi1"),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
+ val wifiAgent = Agent(
+ score = keepScore(),
+ lp = lp("wifi0"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+ )
+ val cellAgentDun = Agent(
+ score = keepScore(),
+ lp = lp("cell0"),
+ nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)
+ )
+ val wifiAgentDun = Agent(
+ score = keepScore(),
+ lp = lp("wifi1"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)
+ )
val cb = TestableNetworkCallback()
- cm.registerNetworkCallback(NetworkRequest.Builder()
- .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .build(),
- cb)
+ cm.registerNetworkCallback(
+ NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ cb
+ )
cb.expectAvailableCallbacks(localAgent.network, validated = false)
val inOrder = inOrder(netd)
@@ -750,8 +917,11 @@
inOrder.verify(netd).ipfwdDisableForwarding(any())
cb.expect<LocalInfoChanged>(localAgent.network) { it.info.upstreamNetwork == null }
- val wifiAgentDun2 = Agent(score = keepScore(), lp = lp("wifi2"),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
+ val wifiAgentDun2 = Agent(
+ score = keepScore(),
+ lp = lp("wifi2"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)
+ )
wifiAgentDun2.connect()
inOrder.verify(netd).ipfwdEnableForwarding(any())
inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi2")
@@ -764,8 +934,11 @@
inOrder.verify(netd).ipfwdDisableForwarding(any())
cb.expect<LocalInfoChanged>(localAgent.network) { it.info.upstreamNetwork == null }
- val wifiAgentDun3 = Agent(score = keepScore(), lp = lp("wifi3"),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
+ val wifiAgentDun3 = Agent(
+ score = keepScore(),
+ lp = lp("wifi3"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)
+ )
wifiAgentDun3.connect()
inOrder.verify(netd).ipfwdEnableForwarding(any())
inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi3")
@@ -800,8 +973,11 @@
cm.registerNetworkCallback(nr, listenCb)
val upstream = if (haveUpstream) {
- Agent(score = keepScore(), lp = lp("wifi0"),
- nc = nc(TRANSPORT_WIFI)).also { it.connect() }
+ Agent(
+ score = keepScore(),
+ lp = lp("wifi0"),
+ nc = nc(TRANSPORT_WIFI)
+ ).also { it.connect() }
} else {
null
}
@@ -809,23 +985,32 @@
// Set up a local agent.
val lnc = FromS(LocalNetworkConfig.Builder().apply {
if (haveUpstream) {
- setUpstreamSelector(NetworkRequest.Builder()
- .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
- .addTransportType(TRANSPORT_WIFI)
- .build())
+ setUpstreamSelector(
+ NetworkRequest.Builder()
+ .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .addTransportType(TRANSPORT_WIFI)
+ .build()
+ )
}
}.build())
- val localAgent = Agent(nc = nc(TRANSPORT_THREAD, NET_CAPABILITY_LOCAL_NETWORK),
+ val localAgent = Agent(
+ nc = nc(TRANSPORT_THREAD, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
lnc = lnc,
score = FromS(NetworkScore.Builder().build())
)
localAgent.connect()
- requestCb.expectAvailableCallbacks(localAgent.network,
- validated = false, upstream = upstream?.network)
- listenCb.expectAvailableCallbacks(localAgent.network,
- validated = false, upstream = upstream?.network)
+ requestCb.expectAvailableCallbacks(
+ localAgent.network,
+ validated = false,
+ upstream = upstream?.network
+ )
+ listenCb.expectAvailableCallbacks(
+ localAgent.network,
+ validated = false,
+ upstream = upstream?.network
+ )
cm.unregisterNetworkCallback(requestCb)
@@ -866,12 +1051,14 @@
doTestLocalNetworkRequest(
request,
enableMatchLocalNetwork = false,
- expectCallback = false)
+ expectCallback = false
+ )
// If ENABLE_MATCH_LOCAL_NETWORK is true, request is satisfied by local network
doTestLocalNetworkRequest(
request,
enableMatchLocalNetwork = true,
- expectCallback = true)
+ expectCallback = true
+ )
}
@Test
@@ -880,10 +1067,12 @@
doTestLocalNetworkRequest(
request,
enableMatchLocalNetwork = false,
- expectCallback = true)
+ expectCallback = true
+ )
doTestLocalNetworkRequest(
request,
enableMatchLocalNetwork = true,
- expectCallback = true)
+ expectCallback = true
+ )
}
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index 9be7d11..6e07ac6 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -104,7 +104,8 @@
doNothing().`when`(networkStack).makeNetworkMonitor(
nmNetworkCaptor.capture(),
any() /* name */,
- nmCbCaptor.capture())
+ nmCbCaptor.capture()
+ )
// Create the actual agent. NetworkAgent is abstract, so make an anonymous subclass.
if (deps.isAtLeastS()) {
@@ -157,8 +158,10 @@
// Instead of forcefully adding NOT_SUSPENDED to all agents like older tools did,
// it's better to let the developer manage it as they see fit but help them
// debug if they forget.
- fail("Could not connect the agent. Did you forget to add " +
- "NET_CAPABILITY_NOT_SUSPENDED ?")
+ fail(
+ "Could not connect the agent. Did you forget to add " +
+ "NET_CAPABILITY_NOT_SUSPENDED ?"
+ )
}
fail("Could not connect the agent. Instrumentation failure ?")
}
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 d7e781e..48333c5 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -169,6 +169,7 @@
it[ConnectivityFlags.DELAY_DESTROY_SOCKETS] = true
it[ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS] = true
it[ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS] = true
+ it[ConnectivityFlags.QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER] = true
}
fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
index a53d430..5e18843 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
@@ -114,8 +114,10 @@
)
doReturn(true).`when`(pm).hasSystemFeature(argThat { supported.contains(it) })
val myPackageName = realContext.packageName
- val myPackageInfo = realContext.packageManager.getPackageInfo(myPackageName,
- PackageManager.GET_PERMISSIONS)
+ val myPackageInfo = realContext.packageManager.getPackageInfo(
+ myPackageName,
+ PackageManager.GET_PERMISSIONS
+ )
// Very high version code so that the checks for the module version will always
// say that it is recent enough. This is the most sensible default, but if some
// test needs to test with different version codes they can re-mock this with a
@@ -123,7 +125,10 @@
myPackageInfo.longVersionCode = 9999999L
doReturn(arrayOf(myPackageName)).`when`(pm).getPackagesForUid(Binder.getCallingUid())
doReturn(myPackageInfo).`when`(pm).getPackageInfoAsUser(
- eq(myPackageName), anyInt(), eq(UserHandle.getCallingUserId()))
+ eq(myPackageName),
+ anyInt(),
+ eq(UserHandle.getCallingUserId())
+ )
doReturn(listOf(myPackageInfo)).`when`(pm)
.getInstalledPackagesAsUser(eq(PackageManager.GET_PERMISSIONS), anyInt())
}
@@ -144,12 +149,19 @@
handler as Handler
val delayMs = ((date as Long) - SystemClock.elapsedRealtime()).coerceAtLeast(0)
if (delayMs > UNREASONABLY_LONG_ALARM_WAIT_MS) {
- fail("Attempting to send msg more than $UNREASONABLY_LONG_ALARM_WAIT_MS" +
- "ms into the future : $delayMs")
+ fail(
+ "Attempting to send msg more than $UNREASONABLY_LONG_ALARM_WAIT_MS" +
+ "ms into the future : $delayMs"
+ )
}
alrmHdlr.postDelayed({ handler.post(wakeupMsg::onAlarm) }, wakeupMsg, delayMs)
- }.`when`(am).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), anyString(),
- any<WakeupMessage>(), any())
+ }.`when`(am).setExact(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+ anyLong(),
+ anyString(),
+ any<WakeupMessage>(),
+ any()
+ )
doAnswer {
alrmHdlr.removeCallbacksAndMessages(it.getArgument<WakeupMessage>(0))
}.`when`(am).cancel(any<WakeupMessage>())
@@ -193,14 +205,20 @@
private val TEST_LINGER_DELAY_MS = 400
private val TEST_NASCENT_DELAY_MS = 300
-internal fun makeConnectivityService(context: Context, netd: INetd, deps: Dependencies,
- mPermDeps: PermissionMonitor.Dependencies) =
+internal fun makeConnectivityService(
+ context: Context,
+ netd: INetd,
+ deps: Dependencies,
+ mPermDeps: PermissionMonitor.Dependencies
+) =
ConnectivityService(
context,
mock<IDnsResolver>(),
mock<IpConnectivityLog>(),
netd,
- deps, mPermDeps).also {
+ deps,
+ mPermDeps
+ ).also {
it.mLingerDelayMs = TEST_LINGER_DELAY_MS
it.mNascentDelayMs = TEST_NASCENT_DELAY_MS
}