Merge "Clarify advertising logs" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index b773ed8..c1bc31e 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -424,6 +424,11 @@
]
}
],
+ "automotive-mumd-presubmit": [
+ {
+ "name": "CtsNetTestCases"
+ }
+ ],
"imports": [
{
"path": "frameworks/base/core/java/android/net"
diff --git a/Tethering/AndroidManifest.xml b/Tethering/AndroidManifest.xml
index 6a363b0..2a6f6d5 100644
--- a/Tethering/AndroidManifest.xml
+++ b/Tethering/AndroidManifest.xml
@@ -32,8 +32,10 @@
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.QUERY_USERS"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
diff --git a/Tethering/apex/permissions/permissions.xml b/Tethering/apex/permissions/permissions.xml
index f26a961..fcb287e 100644
--- a/Tethering/apex/permissions/permissions.xml
+++ b/Tethering/apex/permissions/permissions.xml
@@ -18,8 +18,10 @@
<permissions>
<privapp-permissions package="com.android.networkstack.tethering">
<permission name="android.permission.BLUETOOTH_PRIVILEGED" />
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.QUERY_USERS"/>
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
<permission name="android.permission.TETHER_PRIVILEGED"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
diff --git a/Tethering/common/TetheringLib/api/module-lib-current.txt b/Tethering/common/TetheringLib/api/module-lib-current.txt
index 460c216..a680590 100644
--- a/Tethering/common/TetheringLib/api/module-lib-current.txt
+++ b/Tethering/common/TetheringLib/api/module-lib-current.txt
@@ -46,5 +46,10 @@
method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
}
+ public static final class TetheringManager.TetheringRequest implements android.os.Parcelable {
+ method @FlaggedApi("com.android.net.flags.tethering_request_with_soft_ap_config") @Nullable public String getPackageName();
+ method @FlaggedApi("com.android.net.flags.tethering_request_with_soft_ap_config") public int getUid();
+ }
+
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 411971d..7c7a4e0 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -33,6 +33,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.ArrayMap;
@@ -746,6 +747,7 @@
mBuilderParcel.exemptFromEntitlementCheck = false;
mBuilderParcel.showProvisioningUi = true;
mBuilderParcel.connectivityScope = getDefaultConnectivityScope(type);
+ mBuilderParcel.uid = Process.INVALID_UID;
mBuilderParcel.softApConfig = null;
}
@@ -920,6 +922,47 @@
}
/**
+ * Sets the UID of the app that sent this request. This should always be overridden when
+ * receiving TetheringRequest from an external source.
+ * @hide
+ */
+ public void setUid(int uid) {
+ mRequestParcel.uid = uid;
+ }
+
+ /**
+ * Sets the package name of the app that sent this request. This should always be overridden
+ * when receiving a TetheringRequest from an external source.
+ * @hide
+ */
+ public void setPackageName(String packageName) {
+ mRequestParcel.packageName = packageName;
+ }
+
+ /**
+ * Gets the UID of the app that sent this request. This defaults to
+ * {@link Process#INVALID_UID} if unset.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public int getUid() {
+ return mRequestParcel.uid;
+ }
+
+ /**
+ * Gets the package name of the app that sent this request. This defaults to {@code null} if
+ * unset.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+ @SystemApi(client = MODULE_LIBRARIES)
+ @Nullable
+ public String getPackageName() {
+ return mRequestParcel.packageName;
+ }
+
+ /**
* Get a TetheringRequestParcel from the configuration
* @hide
*/
@@ -935,6 +978,8 @@
+ ", exemptFromEntitlementCheck= " + mRequestParcel.exemptFromEntitlementCheck
+ ", showProvisioningUi= " + mRequestParcel.showProvisioningUi
+ ", softApConfig= " + mRequestParcel.softApConfig
+ + ", uid= " + mRequestParcel.uid
+ + ", packageName= " + mRequestParcel.packageName
+ " ]";
}
@@ -950,7 +995,9 @@
&& parcel.exemptFromEntitlementCheck == otherParcel.exemptFromEntitlementCheck
&& parcel.showProvisioningUi == otherParcel.showProvisioningUi
&& parcel.connectivityScope == otherParcel.connectivityScope
- && Objects.equals(parcel.softApConfig, otherParcel.softApConfig);
+ && Objects.equals(parcel.softApConfig, otherParcel.softApConfig)
+ && parcel.uid == otherParcel.uid
+ && Objects.equals(parcel.packageName, otherParcel.packageName);
}
@Override
@@ -958,7 +1005,8 @@
TetheringRequestParcel parcel = getParcel();
return Objects.hash(parcel.tetheringType, parcel.localIPv4Address,
parcel.staticClientAddress, parcel.exemptFromEntitlementCheck,
- parcel.showProvisioningUi, parcel.connectivityScope, parcel.softApConfig);
+ parcel.showProvisioningUi, parcel.connectivityScope, parcel.softApConfig,
+ parcel.uid, parcel.packageName);
}
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
index ea7a353..789d5bb 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
@@ -31,4 +31,6 @@
boolean showProvisioningUi;
int connectivityScope;
SoftApConfiguration softApConfig;
+ int uid;
+ String packageName;
}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index f60d7a1..a0604f2 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -725,8 +725,12 @@
if (shouldUseWifiP2pDedicatedIp()) return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
- return mRoutingCoordinator.requestDownstreamAddress(mInterfaceType, scope, useLastAddress,
- mIpv4PrefixRequest);
+ if (useLastAddress) {
+ return mRoutingCoordinator.requestStickyDownstreamAddress(mInterfaceType, scope,
+ mIpv4PrefixRequest);
+ }
+
+ return mRoutingCoordinator.requestDownstreamAddress(mIpv4PrefixRequest);
}
private boolean startIPv6() {
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index b88b13b..cd57c8d 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -33,9 +33,12 @@
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.networkstack.apishim.ConstantsShim.ACTION_TETHER_UNSUPPORTED_CARRIER_UI;
import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_NOT_EXPORTED;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -50,9 +53,13 @@
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.SparseIntArray;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.SharedLog;
@@ -85,7 +92,6 @@
// Indicate tethering is not supported by carrier.
private static final int TETHERING_PROVISIONING_CARRIER_UNSUPPORT = 1002;
- private final ComponentName mSilentProvisioningService;
private static final int MS_PER_HOUR = 60 * 60 * 1000;
private static final int DUMP_TIMEOUT = 10_000;
@@ -109,9 +115,115 @@
private boolean mNeedReRunProvisioningUi = false;
private OnTetherProvisioningFailedListener mListener;
private TetheringConfigurationFetcher mFetcher;
+ private final Dependencies mDeps;
+
+ @VisibleForTesting(visibility = PRIVATE)
+ static class Dependencies {
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final SharedLog mLog;
+ private final ComponentName mSilentProvisioningService;
+
+ Dependencies(@NonNull Context context, @NonNull SharedLog log) {
+ mContext = context;
+ mLog = log;
+ mSilentProvisioningService = ComponentName.unflattenFromString(
+ mContext.getResources().getString(R.string.config_wifi_tether_enable));
+ }
+
+ /**
+ * Run the UI-enabled tethering provisioning check.
+ * @param type tethering type from TetheringManager.TETHERING_{@code *}
+ * @param receiver to receive entitlement check result.
+ *
+ * @return the broadcast intent, or null if the current user is not allowed to
+ * perform entitlement check.
+ */
+ @Nullable
+ protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config,
+ ResultReceiver receiver) {
+ if (DBG) mLog.i("runUiTetherProvisioning: " + type);
+
+ Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI);
+ intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
+ intent.putExtra(EXTRA_TETHER_UI_PROVISIONING_APP_NAME, config.provisioningApp);
+ intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
+ intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Only launch entitlement UI for the current user if it is allowed to
+ // change tethering. This usually means the system user or the admin users in HSUM.
+ if (SdkLevel.isAtLeastT()) {
+ // Create a user context for the current foreground user as UserManager#isAdmin()
+ // operates on the context user.
+ final int currentUserId = getCurrentUser();
+ final UserHandle currentUser = UserHandle.of(currentUserId);
+ final Context userContext = mContext.createContextAsUser(currentUser, 0);
+ final UserManager userManager = userContext.getSystemService(UserManager.class);
+
+ if (userManager.isAdminUser()) {
+ mContext.startActivityAsUser(intent, currentUser);
+ } else {
+ mLog.e("Current user (" + currentUserId
+ + ") is not allowed to perform entitlement check.");
+ return null;
+ }
+ } else {
+ // For T- devices, there is no other admin user other than the system user.
+ mContext.startActivity(intent);
+ }
+ return intent;
+ }
+
+ /**
+ * Run no UI tethering provisioning check.
+ * @param type tethering type from TetheringManager.TETHERING_{@code *}
+ */
+ protected Intent runSilentTetherProvisioning(
+ int type, final TetheringConfiguration config, ResultReceiver receiver) {
+ if (DBG) mLog.i("runSilentTetherProvisioning: " + type);
+
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
+ intent.putExtra(EXTRA_RUN_PROVISION, true);
+ intent.putExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION, config.provisioningAppNoUi);
+ intent.putExtra(EXTRA_TETHER_PROVISIONING_RESPONSE, config.provisioningResponse);
+ intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
+ intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId);
+ intent.setComponent(mSilentProvisioningService);
+ // Only admin user can change tethering and SilentTetherProvisioning don't need to
+ // show UI, it is fine to always start setting's background service as system user.
+ mContext.startService(intent);
+ return intent;
+ }
+
+ /**
+ * Create a PendingIntent for the provisioning recheck alarm.
+ * @param pkgName the package name of the PendingIntent.
+ */
+ PendingIntent createRecheckAlarmIntent(final String pkgName) {
+ final Intent intent = new Intent(ACTION_PROVISIONING_ALARM);
+ intent.setPackage(pkgName);
+ return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ /**
+ * Get the current user id.
+ */
+ int getCurrentUser() {
+ return ActivityManager.getCurrentUser();
+ }
+ }
public EntitlementManager(Context ctx, Handler h, SharedLog log,
Runnable callback) {
+ this(ctx, h, log, callback, new Dependencies(ctx, log));
+ }
+
+ @VisibleForTesting(visibility = PRIVATE)
+ EntitlementManager(Context ctx, Handler h, SharedLog log,
+ Runnable callback, @NonNull Dependencies deps) {
mContext = ctx;
mLog = log.forSubComponent(TAG);
mCurrentDownstreams = new BitSet();
@@ -120,6 +232,7 @@
mEntitlementCacheValue = new SparseIntArray();
mPermissionChangeCallback = callback;
mHandler = h;
+ mDeps = deps;
if (SdkLevel.isAtLeastU()) {
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
null, mHandler, RECEIVER_NOT_EXPORTED);
@@ -127,8 +240,6 @@
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
null, mHandler);
}
- mSilentProvisioningService = ComponentName.unflattenFromString(
- mContext.getResources().getString(R.string.config_wifi_tether_enable));
}
public void setOnTetherProvisioningFailedListener(
@@ -382,53 +493,6 @@
}
}
- /**
- * Run no UI tethering provisioning check.
- * @param type tethering type from TetheringManager.TETHERING_{@code *}
- * @param subId default data subscription ID.
- */
- @VisibleForTesting
- protected Intent runSilentTetherProvisioning(
- int type, final TetheringConfiguration config, ResultReceiver receiver) {
- if (DBG) mLog.i("runSilentTetherProvisioning: " + type);
-
- Intent intent = new Intent();
- intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
- intent.putExtra(EXTRA_RUN_PROVISION, true);
- intent.putExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION, config.provisioningAppNoUi);
- intent.putExtra(EXTRA_TETHER_PROVISIONING_RESPONSE, config.provisioningResponse);
- intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
- intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId);
- intent.setComponent(mSilentProvisioningService);
- // Only admin user can change tethering and SilentTetherProvisioning don't need to
- // show UI, it is fine to always start setting's background service as system user.
- mContext.startService(intent);
- return intent;
- }
-
- /**
- * Run the UI-enabled tethering provisioning check.
- * @param type tethering type from TetheringManager.TETHERING_{@code *}
- * @param subId default data subscription ID.
- * @param receiver to receive entitlement check result.
- */
- @VisibleForTesting
- protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config,
- ResultReceiver receiver) {
- if (DBG) mLog.i("runUiTetherProvisioning: " + type);
-
- Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI);
- intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
- intent.putExtra(EXTRA_TETHER_UI_PROVISIONING_APP_NAME, config.provisioningApp);
- intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
- intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // Only launch entitlement UI for system user. Entitlement UI should not appear for other
- // user because only admin user is allowed to change tethering.
- mContext.startActivity(intent);
- return intent;
- }
-
private void runTetheringProvisioning(
boolean showProvisioningUi, int downstreamType, final TetheringConfiguration config) {
if (!config.isCarrierSupportTethering) {
@@ -442,9 +506,9 @@
ResultReceiver receiver =
buildProxyReceiver(downstreamType, showProvisioningUi/* notifyFail */, null);
if (showProvisioningUi) {
- runUiTetherProvisioning(downstreamType, config, receiver);
+ mDeps.runUiTetherProvisioning(downstreamType, config, receiver);
} else {
- runSilentTetherProvisioning(downstreamType, config, receiver);
+ mDeps.runSilentTetherProvisioning(downstreamType, config, receiver);
}
}
@@ -458,20 +522,13 @@
mContext.startActivity(intent);
}
- @VisibleForTesting
- PendingIntent createRecheckAlarmIntent(final String pkgName) {
- final Intent intent = new Intent(ACTION_PROVISIONING_ALARM);
- intent.setPackage(pkgName);
- return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
- }
-
// Not needed to check if this don't run on the handler thread because it's private.
private void scheduleProvisioningRecheck(final TetheringConfiguration config) {
if (mProvisioningRecheckAlarm == null) {
final int period = config.provisioningCheckPeriod;
if (period <= 0) return;
- mProvisioningRecheckAlarm = createRecheckAlarmIntent(mContext.getPackageName());
+ mProvisioningRecheckAlarm = mDeps.createRecheckAlarmIntent(mContext.getPackageName());
AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
Context.ALARM_SERVICE);
long triggerAtMillis = SystemClock.elapsedRealtime() + (period * MS_PER_HOUR);
@@ -697,7 +754,7 @@
receiver.send(cacheValue, null);
} else {
ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver);
- runUiTetherProvisioning(downstream, config, proxy);
+ mDeps.runUiTetherProvisioning(downstream, config, proxy);
}
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 454cbf1..cea7e82 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -55,6 +55,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.PermissionUtils;
import com.android.networkstack.apishim.SettingsShimImpl;
import com.android.networkstack.apishim.common.SettingsShim;
@@ -138,8 +139,10 @@
listener)) {
return;
}
- // TODO(b/216524590): Add UID/packageName of caller to TetheringRequest here
- mTethering.startTethering(new TetheringRequest(request), callerPkg, listener);
+ TetheringRequest external = new TetheringRequest(request);
+ external.setUid(getBinderCallingUid());
+ external.setPackageName(callerPkg);
+ mTethering.startTethering(external, callerPkg, listener);
}
@Override
@@ -238,6 +241,12 @@
final String callingAttributionTag, final boolean onlyAllowPrivileged,
final IIntResultListener listener) {
try {
+ if (!checkPackageNameMatchesUid(getBinderCallingUid(), callerPkg)) {
+ Log.e(TAG, "Package name " + callerPkg + " does not match UID "
+ + getBinderCallingUid());
+ listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ return true;
+ }
if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
onlyAllowPrivileged)) {
listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
@@ -256,6 +265,12 @@
private boolean checkAndNotifyCommonError(final String callerPkg,
final String callingAttributionTag, final ResultReceiver receiver) {
+ if (!checkPackageNameMatchesUid(getBinderCallingUid(), callerPkg)) {
+ Log.e(TAG, "Package name " + callerPkg + " does not match UID "
+ + getBinderCallingUid());
+ receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
+ return true;
+ }
if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */)) {
receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
@@ -290,9 +305,9 @@
if (mTethering.isTetherProvisioningRequired()) return false;
- int uid = Binder.getCallingUid();
+ int uid = getBinderCallingUid();
- // If callerPkg's uid is not same as Binder.getCallingUid(),
+ // If callerPkg's uid is not same as getBinderCallingUid(),
// checkAndNoteWriteSettingsOperation will return false and the operation will be
// denied.
return mService.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg,
@@ -305,6 +320,14 @@
return mService.checkCallingOrSelfPermission(
ACCESS_NETWORK_STATE) == PERMISSION_GRANTED;
}
+
+ private int getBinderCallingUid() {
+ return mService.getBinderCallingUid();
+ }
+
+ private boolean checkPackageNameMatchesUid(final int uid, final String callerPkg) {
+ return mService.checkPackageNameMatchesUid(mService, uid, callerPkg);
+ }
}
/**
@@ -322,6 +345,28 @@
}
/**
+ * Check if the package name matches the uid.
+ */
+ @VisibleForTesting
+ boolean checkPackageNameMatchesUid(@NonNull Context context, int uid,
+ @NonNull String callingPackage) {
+ try {
+ PermissionUtils.enforcePackageNameMatchesUid(context, uid, callingPackage);
+ } catch (SecurityException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Wrapper for the Binder calling UID, used for mocks.
+ */
+ @VisibleForTesting
+ int getBinderCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ /**
* An injection method for testing.
*/
@VisibleForTesting
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
index a744953..087ce44 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -75,6 +75,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.HandlerUtils;
import com.android.networkstack.tethering.UpstreamNetworkState;
import java.util.ArrayList;
@@ -478,10 +479,7 @@
@VisibleForTesting
@NonNull
DataUsage getLastReportedUsageFromUpstreamType(@NonNull UpstreamType type) {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on Handler thread: " + Thread.currentThread().getName());
- }
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
return mLastReportedUpstreamUsage.getOrDefault(type, EMPTY);
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 8f5e6c4..680e81d 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -258,8 +258,8 @@
mIpServer, interfaceParams.index, upstreamPrefixes);
}
reset(mNetd, mBpfCoordinator, mCallback, mRoutingCoordinatorManager);
- when(mRoutingCoordinatorManager.requestDownstreamAddress(anyInt(), anyInt(),
- anyBoolean(), any())).thenReturn(mTestAddress);
+ when(mRoutingCoordinatorManager.requestStickyDownstreamAddress(anyInt(), anyInt(),
+ any())).thenReturn(mTestAddress);
}
@SuppressWarnings("DoNotCall") // Ignore warning for synchronous to call to Thread.run()
@@ -280,8 +280,9 @@
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
- when(mRoutingCoordinatorManager.requestDownstreamAddress(anyInt(), anyInt(),
- anyBoolean(), any())).thenReturn(mTestAddress);
+ when(mRoutingCoordinatorManager.requestStickyDownstreamAddress(anyInt(), anyInt(),
+ any())).thenReturn(mTestAddress);
+ when(mRoutingCoordinatorManager.requestDownstreamAddress(any())).thenReturn(mTestAddress);
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */);
@@ -348,11 +349,11 @@
InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
if (isAtLeastT()) {
inOrder.verify(mRoutingCoordinatorManager)
- .requestDownstreamAddress(
+ .requestStickyDownstreamAddress(
eq(TETHERING_BLUETOOTH),
eq(CONNECTIVITY_SCOPE_GLOBAL),
- eq(true),
any());
+ inOrder.verify(mRoutingCoordinatorManager, never()).requestDownstreamAddress(any());
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
}
@@ -401,8 +402,9 @@
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
- inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
- eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true), any());
+ inOrder.verify(mRoutingCoordinatorManager).requestStickyDownstreamAddress(anyInt(),
+ eq(CONNECTIVITY_SCOPE_GLOBAL), any());
+ inOrder.verify(mRoutingCoordinatorManager, never()).requestDownstreamAddress(any());
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -423,8 +425,9 @@
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
- inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
- eq(CONNECTIVITY_SCOPE_LOCAL), eq(true), any());
+ inOrder.verify(mRoutingCoordinatorManager).requestStickyDownstreamAddress(anyInt(),
+ eq(CONNECTIVITY_SCOPE_LOCAL), any());
+ inOrder.verify(mRoutingCoordinatorManager, never()).requestDownstreamAddress(any());
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -449,7 +452,8 @@
// When using WiFi P2p dedicated IP, the IpServer just picks the IP address without
// requesting for it at RoutingCoordinatorManager.
inOrder.verify(mRoutingCoordinatorManager, never())
- .requestDownstreamAddress(anyInt(), anyInt(), anyBoolean(), any());
+ .requestStickyDownstreamAddress(anyInt(), anyInt(), any());
+ inOrder.verify(mRoutingCoordinatorManager, never()).requestDownstreamAddress(any());
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -731,8 +735,9 @@
final ArgumentCaptor<LinkProperties> lpCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
InOrder inOrder = inOrder(mNetd, mCallback, mRoutingCoordinatorManager);
- inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
- eq(CONNECTIVITY_SCOPE_LOCAL), eq(true), any());
+ inOrder.verify(mRoutingCoordinatorManager).requestStickyDownstreamAddress(anyInt(),
+ eq(CONNECTIVITY_SCOPE_LOCAL), any());
+ inOrder.verify(mRoutingCoordinatorManager, never()).requestDownstreamAddress(any());
inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
// One for ipv4 route, one for ipv6 link local route.
inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@@ -745,13 +750,13 @@
// Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
// onNewPrefixRequest callback.
final LinkAddress newAddress = new LinkAddress("192.168.100.125/24");
- when(mRoutingCoordinatorManager.requestDownstreamAddress(anyInt(), anyInt(),
- anyBoolean(), any())).thenReturn(newAddress);
+ when(mRoutingCoordinatorManager.requestDownstreamAddress(any())).thenReturn(newAddress);
eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
mLooper.dispatchAll();
- inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
- eq(CONNECTIVITY_SCOPE_LOCAL), eq(false), any());
+ inOrder.verify(mRoutingCoordinatorManager, never())
+ .requestStickyDownstreamAddress(anyInt(), anyInt(), any());
+ inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(any());
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
verifyNoMoreInteractions(mCallback);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index c2e1617..8626b18 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -38,6 +38,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@@ -71,11 +72,13 @@
import android.os.ResultReceiver;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.test.TestLooper;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -114,6 +117,7 @@
@Mock private EntitlementManager
.OnTetherProvisioningFailedListener mTetherProvisioningFailedListener;
@Mock private AlarmManager mAlarmManager;
+ @Mock private UserManager mUserManager;
@Mock private PendingIntent mAlarmIntent;
@Rule
@@ -126,9 +130,10 @@
private MockContext mMockContext;
private Runnable mPermissionChangeCallback;
- private WrappedEntitlementManager mEnMgr;
+ private EntitlementManager mEnMgr;
private TetheringConfiguration mConfig;
private MockitoSession mMockingSession;
+ private TestDependencies mDeps;
private class MockContext extends BroadcastInterceptingContext {
MockContext(Context base) {
@@ -143,19 +148,30 @@
@Override
public Object getSystemService(String name) {
if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager;
+ if (Context.USER_SERVICE.equals(name)) return mUserManager;
return super.getSystemService(name);
}
+
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ if (UserManager.class.equals(serviceClass)) return Context.USER_SERVICE;
+ return super.getSystemServiceName(serviceClass);
+ }
+
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return mMockContext; // Return self for easier test injection.
+ }
}
- public class WrappedEntitlementManager extends EntitlementManager {
+ class TestDependencies extends EntitlementManager.Dependencies {
public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKNOWN;
public int uiProvisionCount = 0;
public int silentProvisionCount = 0;
-
- public WrappedEntitlementManager(Context ctx, Handler h, SharedLog log,
- Runnable callback) {
- super(ctx, h, log, callback);
+ TestDependencies(@NonNull Context context,
+ @NonNull SharedLog log) {
+ super(context, log);
}
public void reset() {
@@ -168,8 +184,10 @@
protected Intent runUiTetherProvisioning(int type,
final TetheringConfiguration config, final ResultReceiver receiver) {
Intent intent = super.runUiTetherProvisioning(type, config, receiver);
- assertUiTetherProvisioningIntent(type, config, receiver, intent);
- uiProvisionCount++;
+ if (intent != null) {
+ assertUiTetherProvisioningIntent(type, config, receiver, intent);
+ uiProvisionCount++;
+ }
receiver.send(fakeEntitlementResult, null);
return intent;
}
@@ -195,7 +213,7 @@
Intent intent = super.runSilentTetherProvisioning(type, config, receiver);
assertSilentTetherProvisioning(type, config, intent);
silentProvisionCount++;
- addDownstreamMapping(type, fakeEntitlementResult);
+ mEnMgr.addDownstreamMapping(type, fakeEntitlementResult);
return intent;
}
@@ -217,6 +235,13 @@
assertEquals(TEST_PACKAGE_NAME, pkgName);
return mAlarmIntent;
}
+
+ @Override
+ int getCurrentUser() {
+ // The result is not used, just override to bypass the need of accessing
+ // the static method.
+ return 0;
+ }
}
@Before
@@ -253,11 +278,13 @@
false);
when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
+ doReturn(true).when(mUserManager).isAdminUser();
mMockContext = new MockContext(mContext);
+ mDeps = new TestDependencies(mMockContext, mLog);
mPermissionChangeCallback = spy(() -> { });
- mEnMgr = new WrappedEntitlementManager(mMockContext, new Handler(mLooper.getLooper()), mLog,
- mPermissionChangeCallback);
+ mEnMgr = new EntitlementManager(mMockContext, new Handler(mLooper.getLooper()), mLog,
+ mPermissionChangeCallback, mDeps);
mEnMgr.setOnTetherProvisioningFailedListener(mTetherProvisioningFailedListener);
mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
mEnMgr.setTetheringConfigurationFetcher(() -> {
@@ -320,7 +347,7 @@
@Test
public void testRequestLastEntitlementCacheValue() throws Exception {
// 1. Entitlement check is not required.
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
ResultReceiver receiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -329,8 +356,8 @@
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- mEnMgr.reset();
+ assertEquals(0, mDeps.uiProvisionCount);
+ mDeps.reset();
setupForRequiredProvisioning();
// 2. No cache value and don't need to run entitlement check.
@@ -342,10 +369,10 @@
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- mEnMgr.reset();
+ assertEquals(0, mDeps.uiProvisionCount);
+ mDeps.reset();
// 3. No cache value and ui entitlement check is needed.
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
receiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -354,11 +381,11 @@
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
- assertEquals(1, mEnMgr.uiProvisionCount);
- mEnMgr.reset();
+ assertEquals(1, mDeps.uiProvisionCount);
+ mDeps.reset();
// 4. Cache value is TETHER_ERROR_PROVISIONING_FAILED and don't need to run entitlement
// check.
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
receiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -367,10 +394,10 @@
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- mEnMgr.reset();
+ assertEquals(0, mDeps.uiProvisionCount);
+ mDeps.reset();
// 5. Cache value is TETHER_ERROR_PROVISIONING_FAILED and ui entitlement check is needed.
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
receiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -379,10 +406,10 @@
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
- assertEquals(1, mEnMgr.uiProvisionCount);
- mEnMgr.reset();
+ assertEquals(1, mDeps.uiProvisionCount);
+ mDeps.reset();
// 6. Cache value is TETHER_ERROR_NO_ERROR.
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
receiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -391,8 +418,8 @@
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- mEnMgr.reset();
+ assertEquals(0, mDeps.uiProvisionCount);
+ mDeps.reset();
// 7. Test get value for other downstream type.
receiver = new ResultReceiver(null) {
@Override
@@ -402,10 +429,10 @@
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_USB, receiver, false);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- mEnMgr.reset();
+ assertEquals(0, mDeps.uiProvisionCount);
+ mDeps.reset();
// 8. Test get value for invalid downstream type.
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
receiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -414,8 +441,8 @@
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI_P2P, receiver, true);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- mEnMgr.reset();
+ assertEquals(0, mDeps.uiProvisionCount);
+ mDeps.reset();
}
private void assertPermissionChangeCallback(InOrder inOrder) {
@@ -431,7 +458,7 @@
final InOrder inOrder = inOrder(mPermissionChangeCallback);
setupForRequiredProvisioning();
mEnMgr.notifyUpstream(true);
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
// Permitted: true -> false
@@ -443,7 +470,7 @@
// Permitted: false -> false
assertNoPermissionChange(inOrder);
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
// Permitted: false -> true
@@ -456,21 +483,21 @@
final InOrder inOrder = inOrder(mPermissionChangeCallback);
setupForRequiredProvisioning();
mEnMgr.notifyUpstream(true);
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
// Permitted: true -> false
assertPermissionChangeCallback(inOrder);
assertFalse(mEnMgr.isCellularUpstreamPermitted());
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
mLooper.dispatchAll();
// Permitted: false -> false
assertNoPermissionChange(inOrder);
assertFalse(mEnMgr.isCellularUpstreamPermitted());
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true);
mLooper.dispatchAll();
// Permitted: false -> false
@@ -483,14 +510,14 @@
final InOrder inOrder = inOrder(mPermissionChangeCallback);
setupForRequiredProvisioning();
mEnMgr.notifyUpstream(true);
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
// Permitted: true -> true
assertNoPermissionChange(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
mLooper.dispatchAll();
// Permitted: true -> true
@@ -519,89 +546,89 @@
final InOrder inOrder = inOrder(mPermissionChangeCallback);
setupForRequiredProvisioning();
// 1. start ui provisioning, upstream is mobile
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.notifyUpstream(true);
mLooper.dispatchAll();
mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
mLooper.dispatchAll();
- assertEquals(1, mEnMgr.uiProvisionCount);
- assertEquals(0, mEnMgr.silentProvisionCount);
+ assertEquals(1, mDeps.uiProvisionCount);
+ assertEquals(0, mDeps.silentProvisionCount);
// Permitted: true -> true
assertNoPermissionChange(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
- mEnMgr.reset();
+ mDeps.reset();
// 2. start no-ui provisioning
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- assertEquals(1, mEnMgr.silentProvisionCount);
+ assertEquals(0, mDeps.uiProvisionCount);
+ assertEquals(1, mDeps.silentProvisionCount);
// Permitted: true -> true
assertNoPermissionChange(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
- mEnMgr.reset();
+ mDeps.reset();
// 3. tear down mobile, then start ui provisioning
mEnMgr.notifyUpstream(false);
mLooper.dispatchAll();
mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- assertEquals(0, mEnMgr.silentProvisionCount);
+ assertEquals(0, mDeps.uiProvisionCount);
+ assertEquals(0, mDeps.silentProvisionCount);
assertNoPermissionChange(inOrder);
- mEnMgr.reset();
+ mDeps.reset();
// 4. switch upstream back to mobile
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.notifyUpstream(true);
mLooper.dispatchAll();
- assertEquals(1, mEnMgr.uiProvisionCount);
- assertEquals(0, mEnMgr.silentProvisionCount);
+ assertEquals(1, mDeps.uiProvisionCount);
+ assertEquals(0, mDeps.silentProvisionCount);
// Permitted: true -> true
assertNoPermissionChange(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
- mEnMgr.reset();
+ mDeps.reset();
// 5. tear down mobile, then switch SIM
mEnMgr.notifyUpstream(false);
mLooper.dispatchAll();
mEnMgr.reevaluateSimCardProvisioning(mConfig);
- assertEquals(0, mEnMgr.uiProvisionCount);
- assertEquals(0, mEnMgr.silentProvisionCount);
+ assertEquals(0, mDeps.uiProvisionCount);
+ assertEquals(0, mDeps.silentProvisionCount);
assertNoPermissionChange(inOrder);
- mEnMgr.reset();
+ mDeps.reset();
// 6. switch upstream back to mobile again
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.notifyUpstream(true);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- assertEquals(3, mEnMgr.silentProvisionCount);
+ assertEquals(0, mDeps.uiProvisionCount);
+ assertEquals(3, mDeps.silentProvisionCount);
// Permitted: true -> false
assertPermissionChangeCallback(inOrder);
assertFalse(mEnMgr.isCellularUpstreamPermitted());
- mEnMgr.reset();
+ mDeps.reset();
// 7. start ui provisioning, upstream is mobile, downstream is ethernet
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_ETHERNET, true);
mLooper.dispatchAll();
- assertEquals(1, mEnMgr.uiProvisionCount);
- assertEquals(0, mEnMgr.silentProvisionCount);
+ assertEquals(1, mDeps.uiProvisionCount);
+ assertEquals(0, mDeps.silentProvisionCount);
// Permitted: false -> true
assertPermissionChangeCallback(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
- mEnMgr.reset();
+ mDeps.reset();
// 8. downstream is invalid
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI_P2P, true);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- assertEquals(0, mEnMgr.silentProvisionCount);
+ assertEquals(0, mDeps.uiProvisionCount);
+ assertEquals(0, mDeps.silentProvisionCount);
assertNoPermissionChange(inOrder);
- mEnMgr.reset();
+ mDeps.reset();
}
@Test
@@ -609,16 +636,43 @@
setupForRequiredProvisioning();
verify(mTetherProvisioningFailedListener, times(0))
.onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON);
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.notifyUpstream(true);
mLooper.dispatchAll();
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
- assertEquals(1, mEnMgr.uiProvisionCount);
+ assertEquals(1, mDeps.uiProvisionCount);
verify(mTetherProvisioningFailedListener, times(1))
.onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON);
}
+ @IgnoreUpTo(SC_V2)
+ @Test
+ public void testUiProvisioningMultiUser_aboveT() {
+ doTestUiProvisioningMultiUser(true, 1);
+ doTestUiProvisioningMultiUser(false, 0);
+ }
+
+ @IgnoreAfter(SC_V2)
+ @Test
+ public void testUiProvisioningMultiUser_belowT() {
+ doTestUiProvisioningMultiUser(true, 1);
+ doTestUiProvisioningMultiUser(false, 1);
+ }
+
+ private void doTestUiProvisioningMultiUser(boolean isAdminUser, int expectedUiProvisionCount) {
+ setupForRequiredProvisioning();
+ doReturn(isAdminUser).when(mUserManager).isAdminUser();
+
+ mDeps.reset();
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mEnMgr.notifyUpstream(true);
+ mLooper.dispatchAll();
+ mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
+ mLooper.dispatchAll();
+ assertEquals(expectedUiProvisionCount, mDeps.uiProvisionCount);
+ }
+
@Test
public void testsetExemptedDownstreamType() throws Exception {
setupForRequiredProvisioning();
@@ -631,7 +685,7 @@
assertTrue(mEnMgr.isCellularUpstreamPermitted());
// If second downstream run entitlement check fail, cellular upstream is not permitted.
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.notifyUpstream(true);
mLooper.dispatchAll();
mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
@@ -639,7 +693,7 @@
assertFalse(mEnMgr.isCellularUpstreamPermitted());
// When second downstream is down, exempted downstream can use cellular upstream.
- assertEquals(1, mEnMgr.uiProvisionCount);
+ assertEquals(1, mDeps.uiProvisionCount);
verify(mTetherProvisioningFailedListener).onTetherProvisioningFailed(TETHERING_USB,
FAILED_TETHERING_REASON);
mEnMgr.stopProvisioningIfNeeded(TETHERING_USB);
@@ -660,7 +714,7 @@
setupForRequiredProvisioning();
assertFalse(mEnMgr.isCellularUpstreamPermitted());
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.notifyUpstream(true);
mLooper.dispatchAll();
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
@@ -682,7 +736,7 @@
throws Exception {
setupCarrierConfig(false);
setupForRequiredProvisioning();
- mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+ mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
ResultReceiver receiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -691,8 +745,8 @@
};
mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
mLooper.dispatchAll();
- assertEquals(0, mEnMgr.uiProvisionCount);
- mEnMgr.reset();
+ assertEquals(0, mDeps.uiProvisionCount);
+ mDeps.reset();
}
@Test
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 3c07580..7fcc5f1 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
@@ -32,6 +32,8 @@
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 int mMockCallingUid;
@Override
public IBinder onBind(Intent intent) {
@@ -61,6 +63,17 @@
return super.checkCallingOrSelfPermission(permission);
}
+ @Override
+ boolean checkPackageNameMatchesUid(@NonNull Context context, int uid,
+ @NonNull String callingPackage) {
+ return mMockedPackageUids.getOrDefault(callingPackage, 0) == uid;
+ }
+
+ @Override
+ int getBinderCallingUid() {
+ return mMockCallingUid;
+ }
+
public Tethering getTethering() {
return mTethering;
}
@@ -91,5 +104,19 @@
mMockedPermissions.put(permission, granted);
}
}
+
+ /**
+ * Mock a package name matching a uid.
+ */
+ public void setPackageNameUid(String packageName, int uid) {
+ mMockedPackageUids.put(packageName, uid);
+ }
+
+ /**
+ * Mock a package name matching a uid.
+ */
+ public void setCallingUid(int uid) {
+ mMockCallingUid = uid;
+ }
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 1ab5766..1608e1a 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -134,13 +134,18 @@
spy(new PrivateAddressCoordinator(mConnectivityMgr::getAllNetworks, mDeps));
}
- private LinkAddress requestDownstreamAddress(
- final IpServer ipServer, int scope, boolean useLastAddress) throws Exception {
+ private LinkAddress requestStickyDownstreamAddress(final IpServer ipServer, int scope)
+ throws Exception {
+ final LinkAddress address =
+ mPrivateAddressCoordinator.requestStickyDownstreamAddress(
+ ipServer.interfaceType(), scope, ipServer.getIpv4PrefixRequest());
+ when(ipServer.getAddress()).thenReturn(address);
+ return address;
+ }
+
+ private LinkAddress requestDownstreamAddress(final IpServer ipServer) throws Exception {
final LinkAddress address =
mPrivateAddressCoordinator.requestDownstreamAddress(
- ipServer.interfaceType(),
- scope,
- useLastAddress,
ipServer.getIpv4PrefixRequest());
when(ipServer.getAddress()).thenReturn(address);
return address;
@@ -158,19 +163,16 @@
@Test
public void testRequestDownstreamAddressWithoutUsingLastAddress() throws Exception {
final IpPrefix bluetoothPrefix = asIpPrefix(mBluetoothAddress);
- final LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
+ final LinkAddress address = requestDownstreamAddress(mHotspotIpServer);
final IpPrefix hotspotPrefix = asIpPrefix(address);
assertNotEquals(hotspotPrefix, bluetoothPrefix);
- final LinkAddress newAddress = requestDownstreamAddress(mHotspotIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
+ final LinkAddress newAddress = requestDownstreamAddress(mHotspotIpServer);
final IpPrefix newHotspotPrefix = asIpPrefix(newAddress);
assertNotEquals(hotspotPrefix, newHotspotPrefix);
assertNotEquals(bluetoothPrefix, newHotspotPrefix);
- final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
+ final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer);
final IpPrefix usbPrefix = asIpPrefix(usbAddress);
assertNotEquals(usbPrefix, bluetoothPrefix);
assertNotEquals(usbPrefix, newHotspotPrefix);
@@ -184,8 +186,7 @@
// - Test bluetooth prefix is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(mBluetoothAddress.getAddress().getAddress()));
- final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
+ final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer);
final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
releaseDownstream(mHotspotIpServer);
@@ -193,8 +194,7 @@
// - Test previous enabled hotspot prefix(cached prefix) is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(hotspotAddress.getAddress().getAddress()));
- final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
+ final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer);
final IpPrefix usbPrefix = asIpPrefix(usbAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), usbPrefix);
assertNotEquals(hotspotPrefix, usbPrefix);
@@ -203,8 +203,7 @@
// - Test wifi p2p prefix is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
- final LinkAddress etherAddress = requestDownstreamAddress(mEthernetIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
+ final LinkAddress etherAddress = requestDownstreamAddress(mEthernetIpServer);
final IpPrefix etherPrefix = asIpPrefix(etherAddress);
assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
@@ -214,20 +213,20 @@
@Test
public void testRequestLastDownstreamAddress() throws Exception {
- final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
+ final LinkAddress hotspotAddress =
+ requestStickyDownstreamAddress(mHotspotIpServer, CONNECTIVITY_SCOPE_GLOBAL);
- final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
+ final LinkAddress usbAddress =
+ requestStickyDownstreamAddress(mUsbIpServer, CONNECTIVITY_SCOPE_GLOBAL);
releaseDownstream(mHotspotIpServer);
releaseDownstream(mUsbIpServer);
- final LinkAddress newHotspotAddress = requestDownstreamAddress(mHotspotIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
+ final LinkAddress newHotspotAddress =
+ requestStickyDownstreamAddress(mHotspotIpServer, CONNECTIVITY_SCOPE_GLOBAL);
assertEquals(hotspotAddress, newHotspotAddress);
- final LinkAddress newUsbAddress = requestDownstreamAddress(mUsbIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
+ final LinkAddress newUsbAddress =
+ requestStickyDownstreamAddress(mUsbIpServer, CONNECTIVITY_SCOPE_GLOBAL);
assertEquals(usbAddress, newUsbAddress);
final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
@@ -299,8 +298,8 @@
}
private void assertReseveredWifiP2pPrefix() throws Exception {
- LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
+ LinkAddress address =
+ requestStickyDownstreamAddress(mHotspotIpServer, CONNECTIVITY_SCOPE_GLOBAL);
final IpPrefix hotspotPrefix = asIpPrefix(address);
final IpPrefix legacyWifiP2pPrefix = asIpPrefix(mLegacyWifiP2pAddress);
assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
@@ -309,12 +308,12 @@
@Test
public void testEnableSapAndLohsConcurrently() throws Exception {
- final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
+ final LinkAddress hotspotAddress =
+ requestStickyDownstreamAddress(mHotspotIpServer, CONNECTIVITY_SCOPE_GLOBAL);
assertNotNull(hotspotAddress);
- final LinkAddress localHotspotAddress = requestDownstreamAddress(mLocalHotspotIpServer,
- CONNECTIVITY_SCOPE_LOCAL, true /* useLastAddress */);
+ final LinkAddress localHotspotAddress =
+ requestStickyDownstreamAddress(mLocalHotspotIpServer, CONNECTIVITY_SCOPE_LOCAL);
assertNotNull(localHotspotAddress);
final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
@@ -353,8 +352,7 @@
mPrivateAddressCoordinator =
spy(new PrivateAddressCoordinator(mConnectivityMgr::getAllNetworks, mDeps));
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomIntForPrefixBase);
- final LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
- CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
+ final LinkAddress address = requestDownstreamAddress(mHotspotIpServer);
final IpPrefix prefixBase = new IpPrefix(expected);
assertTrue(address + " is not part of " + prefixBase,
prefixBase.containsPrefix(asIpPrefix(address)));
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 c0d7ad4..1988311 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -34,6 +34,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -79,6 +80,7 @@
public final class TetheringServiceTest {
private static final String TEST_IFACE_NAME = "test_wlan0";
private static final String TEST_CALLER_PKG = "com.android.shell";
+ private static final int TEST_CALLER_UID = 1234;
private static final String TEST_ATTRIBUTION_TAG = null;
@Mock private ITetheringEventCallback mITetheringEventCallback;
@Rule public ServiceTestRule mServiceTestRule;
@@ -128,6 +130,8 @@
mTetheringConnector = ITetheringConnector.Stub.asInterface(mMockConnector.getIBinder());
final MockTetheringService service = mMockConnector.getService();
mTethering = service.getTethering();
+ mMockConnector.setCallingUid(TEST_CALLER_UID);
+ mMockConnector.setPackageNameUid(TEST_CALLER_PKG, TEST_CALLER_UID);
}
@After
@@ -330,6 +334,16 @@
});
runAsTetherPrivileged((result) -> {
+ String wrongPackage = "wrong.package";
+ mTetheringConnector.startTethering(request, wrongPackage,
+ TEST_ATTRIBUTION_TAG, result);
+ verify(mTethering, never()).startTethering(
+ eq(new TetheringRequest(request)), eq(wrongPackage), eq(result));
+ result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ verifyNoMoreInteractionsForTethering();
+ });
+
+ runAsTetherPrivileged((result) -> {
runStartTethering(result, request);
verifyNoMoreInteractionsForTethering();
});
@@ -445,6 +459,13 @@
verifyNoMoreInteractionsForTethering();
});
+ runAsTetherPrivileged((none) -> {
+ mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
+ true /* showEntitlementUi */, "wrong.package", TEST_ATTRIBUTION_TAG);
+ result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ verifyNoMoreInteractions(mTethering);
+ });
+
runAsWriteSettings((none) -> {
runRequestLatestTetheringEntitlementResult();
verify(mTethering).isTetherProvisioningRequired();
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 08129eb..5f8f0e3 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -500,7 +500,6 @@
@FlaggedApi("com.android.net.thread.flags.configuration_enabled") public final class ThreadConfiguration implements android.os.Parcelable {
method public int describeContents();
- method public boolean isDhcpv6PdEnabled();
method public boolean isNat64Enabled();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.thread.ThreadConfiguration> CREATOR;
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
index fd73b29..16f32c4 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -73,7 +73,9 @@
new CertificateTransparencyInstaller());
}
- void registerReceiver() {
+ void initialize() {
+ mInstaller.addCompatibilityVersion(Config.COMPATIBILITY_VERSION);
+
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
mContext.registerReceiver(this, intentFilter, Context.RECEIVER_EXPORTED);
@@ -185,7 +187,7 @@
String contentUrl = mDataStore.getProperty(Config.CONTENT_URL_PENDING);
String metadataUrl = mDataStore.getProperty(Config.METADATA_URL_PENDING);
try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
- success = mInstaller.install(inputStream, version);
+ success = mInstaller.install(Config.COMPATIBILITY_VERSION, inputStream, version);
} catch (IOException e) {
Log.e(TAG, "Could not install new content", e);
return;
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
index 914af06..0ae982d 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
@@ -43,7 +43,7 @@
void initialize() {
mDataStore.load();
- mCertificateTransparencyDownloader.registerReceiver();
+ mCertificateTransparencyDownloader.initialize();
DeviceConfig.addOnPropertiesChangedListener(
Config.NAMESPACE_NETWORK_SECURITY, Executors.newSingleThreadExecutor(), this);
if (Config.DEBUG) {
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
index 82dcadf..4ca97eb 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
@@ -15,148 +15,78 @@
*/
package com.android.server.net.ct;
-import android.annotation.SuppressLint;
-import android.system.ErrnoException;
-import android.system.Os;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
/** Installer of CT log lists. */
public class CertificateTransparencyInstaller {
private static final String TAG = "CertificateTransparencyInstaller";
- private static final String CT_DIR_NAME = "/data/misc/keychain/ct/";
- static final String LOGS_DIR_PREFIX = "logs-";
- static final String LOGS_LIST_FILE_NAME = "log_list.json";
- static final String CURRENT_DIR_SYMLINK_NAME = "current";
+ private final Map<String, CompatibilityVersion> mCompatVersions = new HashMap<>();
- private final File mCertificateTransparencyDir;
- private final File mCurrentDirSymlink;
+ // The CT root directory.
+ private final File mRootDirectory;
- CertificateTransparencyInstaller(File certificateTransparencyDir) {
- mCertificateTransparencyDir = certificateTransparencyDir;
- mCurrentDirSymlink = new File(certificateTransparencyDir, CURRENT_DIR_SYMLINK_NAME);
+ public CertificateTransparencyInstaller(File rootDirectory) {
+ mRootDirectory = rootDirectory;
}
- CertificateTransparencyInstaller() {
- this(new File(CT_DIR_NAME));
+ public CertificateTransparencyInstaller(String rootDirectoryPath) {
+ this(new File(rootDirectoryPath));
+ }
+
+ public CertificateTransparencyInstaller() {
+ this(Config.CT_ROOT_DIRECTORY_PATH);
+ }
+
+ void addCompatibilityVersion(String versionName) {
+ removeCompatibilityVersion(versionName);
+ CompatibilityVersion newCompatVersion =
+ new CompatibilityVersion(new File(mRootDirectory, versionName));
+ mCompatVersions.put(versionName, newCompatVersion);
+ }
+
+ void removeCompatibilityVersion(String versionName) {
+ CompatibilityVersion compatVersion = mCompatVersions.remove(versionName);
+ if (compatVersion != null && !compatVersion.delete()) {
+ Log.w(TAG, "Could not delete compatibility version directory.");
+ }
+ }
+
+ CompatibilityVersion getCompatibilityVersion(String versionName) {
+ return mCompatVersions.get(versionName);
}
/**
* Install a new log list to use during SCT verification.
*
+ * @param compatibilityVersion the compatibility version of the new log list
* @param newContent an input stream providing the log list
- * @param version the version of the new log list
+ * @param version the minor version of the new log list
* @return true if the log list was installed successfully, false otherwise.
* @throws IOException if the list cannot be saved in the CT directory.
*/
- public boolean install(InputStream newContent, String version) throws IOException {
- // To support atomically replacing the old configuration directory with the new there's a
- // bunch of steps. We create a new directory with the logs and then do an atomic update of
- // the current symlink to point to the new directory.
- // 1. Ensure that the update dir exists and is readable.
- makeDir(mCertificateTransparencyDir);
-
- File newLogsDir = new File(mCertificateTransparencyDir, LOGS_DIR_PREFIX + version);
- // 2. Handle the corner case where the new directory already exists.
- if (newLogsDir.exists()) {
- // If the symlink has already been updated then the update died between steps 6 and 7
- // and so we cannot delete the directory since it is in use.
- if (newLogsDir.getCanonicalPath().equals(mCurrentDirSymlink.getCanonicalPath())) {
- deleteOldLogDirectories();
- return false;
- }
- // If the symlink has not been updated then the previous installation failed and this is
- // a re-attempt. Clean-up leftover files and try again.
- deleteContentsAndDir(newLogsDir);
- }
- try {
- // 3. Create /data/misc/keychain/ct/logs-<new_version>/ .
- makeDir(newLogsDir);
-
- // 4. Move the log list json file in logs-<new_version>/ .
- File logListFile = new File(newLogsDir, LOGS_LIST_FILE_NAME);
- if (Files.copy(newContent, logListFile.toPath()) == 0) {
- throw new IOException("The log list appears empty");
- }
- setWorldReadable(logListFile);
-
- // 5. Create temp symlink. We rename this to the target symlink to get an atomic update.
- File tempSymlink = new File(mCertificateTransparencyDir, "new_symlink");
- try {
- Os.symlink(newLogsDir.getCanonicalPath(), tempSymlink.getCanonicalPath());
- } catch (ErrnoException e) {
- throw new IOException("Failed to create symlink", e);
- }
-
- // 6. Update the symlink target, this is the actual update step.
- tempSymlink.renameTo(mCurrentDirSymlink.getAbsoluteFile());
- } catch (IOException | RuntimeException e) {
- deleteContentsAndDir(newLogsDir);
- throw e;
- }
- Log.i(TAG, "CT log directory updated to " + newLogsDir.getAbsolutePath());
- // 7. Cleanup
- deleteOldLogDirectories();
- return true;
- }
-
- private void makeDir(File dir) throws IOException {
- dir.mkdir();
- if (!dir.isDirectory()) {
- throw new IOException("Unable to make directory " + dir.getCanonicalPath());
- }
- setWorldReadable(dir);
- }
-
- // CT files and directories are readable by all apps.
- @SuppressLint("SetWorldReadable")
- private void setWorldReadable(File file) throws IOException {
- if (!file.setReadable(true, false)) {
- throw new IOException("Failed to set " + file.getCanonicalPath() + " readable");
- }
- }
-
- private void deleteOldLogDirectories() throws IOException {
- if (!mCertificateTransparencyDir.exists()) {
- return;
- }
- File currentTarget = mCurrentDirSymlink.getCanonicalFile();
- for (File file : mCertificateTransparencyDir.listFiles()) {
- if (!currentTarget.equals(file.getCanonicalFile())
- && file.getName().startsWith(LOGS_DIR_PREFIX)) {
- deleteContentsAndDir(file);
- }
- }
- }
-
- static boolean deleteContentsAndDir(File dir) {
- if (deleteContents(dir)) {
- return dir.delete();
- } else {
+ public boolean install(String compatibilityVersion, InputStream newContent, String version)
+ throws IOException {
+ CompatibilityVersion compatVersion = mCompatVersions.get(compatibilityVersion);
+ if (compatVersion == null) {
+ Log.e(TAG, "No compatibility version for " + compatibilityVersion);
return false;
}
- }
+ // Ensure root directory exists and is readable.
+ DirectoryUtils.makeDir(mRootDirectory);
- private static boolean deleteContents(File dir) {
- File[] files = dir.listFiles();
- boolean success = true;
- if (files != null) {
- for (File file : files) {
- if (file.isDirectory()) {
- success &= deleteContents(file);
- }
- if (!file.delete()) {
- Log.w(TAG, "Failed to delete " + file);
- success = false;
- }
- }
+ if (!compatVersion.install(newContent, version)) {
+ Log.e(TAG, "Failed to install logs for compatibility version " + compatibilityVersion);
+ return false;
}
- return success;
+ Log.i(TAG, "New logs installed at " + compatVersion.getLogsDir());
+ return true;
}
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
new file mode 100644
index 0000000..27488b5
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net.ct;
+
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+
+/** Represents a compatibility version directory. */
+class CompatibilityVersion {
+
+ static final String LOGS_DIR_PREFIX = "logs-";
+ static final String LOGS_LIST_FILE_NAME = "log_list.json";
+
+ private static final String CURRENT_LOGS_DIR_SYMLINK_NAME = "current";
+
+ private final File mRootDirectory;
+ private final File mCurrentLogsDirSymlink;
+
+ private File mCurrentLogsDir = null;
+
+ CompatibilityVersion(File rootDirectory) {
+ mRootDirectory = rootDirectory;
+ mCurrentLogsDirSymlink = new File(mRootDirectory, CURRENT_LOGS_DIR_SYMLINK_NAME);
+ }
+
+ /**
+ * Installs a log list within this compatibility version directory.
+ *
+ * @param newContent an input stream providing the log list
+ * @param version the version number of the log list
+ * @return true if the log list was installed successfully, false otherwise.
+ * @throws IOException if the list cannot be saved in the CT directory.
+ */
+ boolean install(InputStream newContent, String version) throws IOException {
+ // To support atomically replacing the old configuration directory with the new there's a
+ // bunch of steps. We create a new directory with the logs and then do an atomic update of
+ // the current symlink to point to the new directory.
+ // 1. Ensure that the root directory exists and is readable.
+ DirectoryUtils.makeDir(mRootDirectory);
+
+ File newLogsDir = new File(mRootDirectory, LOGS_DIR_PREFIX + version);
+ // 2. Handle the corner case where the new directory already exists.
+ if (newLogsDir.exists()) {
+ // If the symlink has already been updated then the update died between steps 6 and 7
+ // and so we cannot delete the directory since it is in use.
+ if (newLogsDir.getCanonicalPath().equals(mCurrentLogsDirSymlink.getCanonicalPath())) {
+ deleteOldLogDirectories();
+ return false;
+ }
+ // If the symlink has not been updated then the previous installation failed and this is
+ // a re-attempt. Clean-up leftover files and try again.
+ DirectoryUtils.removeDir(newLogsDir);
+ }
+ try {
+ // 3. Create a new logs-<new_version>/ directory to store the new list.
+ DirectoryUtils.makeDir(newLogsDir);
+
+ // 4. Move the log list json file in logs-<new_version>/ .
+ File logListFile = new File(newLogsDir, LOGS_LIST_FILE_NAME);
+ if (Files.copy(newContent, logListFile.toPath()) == 0) {
+ throw new IOException("The log list appears empty");
+ }
+ DirectoryUtils.setWorldReadable(logListFile);
+
+ // 5. Create temp symlink. We rename this to the target symlink to get an atomic update.
+ File tempSymlink = new File(mRootDirectory, "new_symlink");
+ try {
+ Os.symlink(newLogsDir.getCanonicalPath(), tempSymlink.getCanonicalPath());
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to create symlink", e);
+ }
+
+ // 6. Update the symlink target, this is the actual update step.
+ tempSymlink.renameTo(mCurrentLogsDirSymlink.getAbsoluteFile());
+ } catch (IOException | RuntimeException e) {
+ DirectoryUtils.removeDir(newLogsDir);
+ throw e;
+ }
+ // 7. Cleanup
+ mCurrentLogsDir = newLogsDir;
+ deleteOldLogDirectories();
+ return true;
+ }
+
+ File getRootDir() {
+ return mRootDirectory;
+ }
+
+ File getLogsDir() {
+ return mCurrentLogsDir;
+ }
+
+ File getLogsDirSymlink() {
+ return mCurrentLogsDirSymlink;
+ }
+
+ File getLogsFile() {
+ return new File(mCurrentLogsDir, LOGS_LIST_FILE_NAME);
+ }
+
+ boolean delete() {
+ return DirectoryUtils.removeDir(mRootDirectory);
+ }
+
+ private void deleteOldLogDirectories() throws IOException {
+ if (!mRootDirectory.exists()) {
+ return;
+ }
+ File currentTarget = mCurrentLogsDirSymlink.getCanonicalFile();
+ for (File file : mRootDirectory.listFiles()) {
+ if (!currentTarget.equals(file.getCanonicalFile())
+ && file.getName().startsWith(LOGS_DIR_PREFIX)) {
+ DirectoryUtils.removeDir(file);
+ }
+ }
+ }
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/Config.java b/networksecurity/service/src/com/android/server/net/ct/Config.java
index 611a5c7..242f13a 100644
--- a/networksecurity/service/src/com/android/server/net/ct/Config.java
+++ b/networksecurity/service/src/com/android/server/net/ct/Config.java
@@ -33,6 +33,10 @@
private static final String PREFERENCES_FILE_NAME = "ct.preferences";
static final File PREFERENCES_FILE = new File(DEVICE_PROTECTED_DATA_DIR, PREFERENCES_FILE_NAME);
+ // CT directory
+ static final String CT_ROOT_DIRECTORY_PATH = "/data/misc/keychain/ct/";
+ static final String COMPATIBILITY_VERSION = "v1";
+
// Phenotype flags
static final String NAMESPACE_NETWORK_SECURITY = "network_security";
private static final String FLAGS_PREFIX = "CertificateTransparencyLogList__";
diff --git a/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java b/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
new file mode 100644
index 0000000..e3b4124
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net.ct;
+
+import android.annotation.SuppressLint;
+
+import java.io.File;
+import java.io.IOException;
+
+/** Utility class to manipulate CT directories. */
+class DirectoryUtils {
+
+ static void makeDir(File dir) throws IOException {
+ dir.mkdir();
+ if (!dir.isDirectory()) {
+ throw new IOException("Unable to make directory " + dir.getCanonicalPath());
+ }
+ setWorldReadable(dir);
+ }
+
+ // CT files and directories are readable by all apps.
+ @SuppressLint("SetWorldReadable")
+ static void setWorldReadable(File file) throws IOException {
+ if (!file.setReadable(true, false)) {
+ throw new IOException("Failed to set " + file.getCanonicalPath() + " readable");
+ }
+ }
+
+ static boolean removeDir(File dir) {
+ return deleteContentsAndDir(dir);
+ }
+
+ private static boolean deleteContentsAndDir(File dir) {
+ if (deleteContents(dir)) {
+ return dir.delete();
+ } else {
+ return false;
+ }
+ }
+
+ private static boolean deleteContents(File dir) {
+ File[] files = dir.listFiles();
+ boolean success = true;
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ success &= deleteContents(file);
+ }
+ if (!file.delete()) {
+ success = false;
+ }
+ }
+ }
+ return success;
+ }
+}
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
index 1aad028..df02446 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
@@ -159,7 +159,9 @@
Base64.getEncoder().encodeToString(mPublicKey.getEncoded()));
setUpDownloadComplete(version, metadataId, metadataUri, contentId, contentUri);
- when(mCertificateTransparencyInstaller.install(any(), eq(version))).thenReturn(true);
+ when(mCertificateTransparencyInstaller.install(
+ eq(Config.COMPATIBILITY_VERSION), any(), eq(version)))
+ .thenReturn(true);
assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
@@ -168,7 +170,8 @@
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
- verify(mCertificateTransparencyInstaller, times(1)).install(any(), eq(version));
+ verify(mCertificateTransparencyInstaller, times(1))
+ .install(eq(Config.COMPATIBILITY_VERSION), any(), eq(version));
assertThat(mDataStore.getProperty(Config.VERSION)).isEqualTo(version);
assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isEqualTo(contentUri.toString());
assertThat(mDataStore.getProperty(Config.METADATA_URL)).isEqualTo(metadataUri.toString());
@@ -185,7 +188,9 @@
Uri metadataUri = Uri.fromFile(metadataFile);
setUpDownloadComplete(version, metadataId, metadataUri, contentId, contentUri);
- when(mCertificateTransparencyInstaller.install(any(), eq(version))).thenReturn(false);
+ when(mCertificateTransparencyInstaller.install(
+ eq(Config.COMPATIBILITY_VERSION), any(), eq(version)))
+ .thenReturn(false);
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
@@ -208,7 +213,8 @@
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
- verify(mCertificateTransparencyInstaller, never()).install(any(), eq(version));
+ verify(mCertificateTransparencyInstaller, never())
+ .install(eq(Config.COMPATIBILITY_VERSION), any(), eq(version));
assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
@@ -230,7 +236,8 @@
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
- verify(mCertificateTransparencyInstaller, never()).install(any(), eq(version));
+ verify(mCertificateTransparencyInstaller, never())
+ .install(eq(Config.COMPATIBILITY_VERSION), any(), eq(version));
assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java
index bfb8bdf..50d3f23 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java
@@ -17,11 +17,9 @@
import static com.google.common.truth.Truth.assertThat;
-import android.system.ErrnoException;
-import android.system.Os;
-
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,98 +37,134 @@
@RunWith(JUnit4.class)
public class CertificateTransparencyInstallerTest {
+ private static final String TEST_VERSION = "test-v1";
+
private File mTestDir =
new File(
InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
"test-dir");
- private File mTestSymlink =
- new File(mTestDir, CertificateTransparencyInstaller.CURRENT_DIR_SYMLINK_NAME);
private CertificateTransparencyInstaller mCertificateTransparencyInstaller =
new CertificateTransparencyInstaller(mTestDir);
@Before
public void setUp() {
- CertificateTransparencyInstaller.deleteContentsAndDir(mTestDir);
+ mCertificateTransparencyInstaller.addCompatibilityVersion(TEST_VERSION);
+ }
+
+ @After
+ public void tearDown() {
+ mCertificateTransparencyInstaller.removeCompatibilityVersion(TEST_VERSION);
+ DirectoryUtils.removeDir(mTestDir);
+ }
+
+ @Test
+ public void testCompatibilityVersion_installSuccessful() throws IOException {
+ assertThat(mTestDir.mkdir()).isTrue();
+ String content = "i_am_compatible";
+ String version = "i_am_version";
+ CompatibilityVersion compatVersion =
+ mCertificateTransparencyInstaller.getCompatibilityVersion(TEST_VERSION);
+
+ try (InputStream inputStream = asStream(content)) {
+ assertThat(compatVersion.install(inputStream, version)).isTrue();
+ }
+ File logsDir = compatVersion.getLogsDir();
+ assertThat(logsDir.exists()).isTrue();
+ assertThat(logsDir.isDirectory()).isTrue();
+ assertThat(logsDir.getAbsolutePath())
+ .startsWith(mTestDir.getAbsolutePath() + "/" + TEST_VERSION);
+ File logsListFile = compatVersion.getLogsFile();
+ assertThat(logsListFile.exists()).isTrue();
+ assertThat(logsListFile.getAbsolutePath()).startsWith(logsDir.getAbsolutePath());
+ assertThat(readAsString(logsListFile)).isEqualTo(content);
+ File logsSymlink = compatVersion.getLogsDirSymlink();
+ assertThat(logsSymlink.exists()).isTrue();
+ assertThat(logsSymlink.isDirectory()).isTrue();
+ assertThat(logsSymlink.getAbsolutePath())
+ .startsWith(mTestDir.getAbsolutePath() + "/" + TEST_VERSION + "/current");
+ assertThat(logsSymlink.getCanonicalPath()).isEqualTo(logsDir.getCanonicalPath());
+
+ assertThat(compatVersion.delete()).isTrue();
+ assertThat(logsDir.exists()).isFalse();
+ assertThat(logsSymlink.exists()).isFalse();
+ assertThat(logsListFile.exists()).isFalse();
+ }
+
+ @Test
+ public void testCompatibilityVersion_versionInstalledFailed() throws IOException {
+ assertThat(mTestDir.mkdir()).isTrue();
+
+ CompatibilityVersion compatVersion =
+ mCertificateTransparencyInstaller.getCompatibilityVersion(TEST_VERSION);
+ File rootDir = compatVersion.getRootDir();
+ assertThat(rootDir.mkdir()).isTrue();
+
+ String existingVersion = "666";
+ File existingLogDir =
+ new File(rootDir, CompatibilityVersion.LOGS_DIR_PREFIX + existingVersion);
+ assertThat(existingLogDir.mkdir()).isTrue();
+
+ String existingContent = "somebody_tried_to_install_me_but_failed_halfway_through";
+ File logsListFile = new File(existingLogDir, CompatibilityVersion.LOGS_LIST_FILE_NAME);
+ assertThat(logsListFile.createNewFile()).isTrue();
+ writeToFile(logsListFile, existingContent);
+
+ String newContent = "i_am_the_real_content";
+ try (InputStream inputStream = asStream(newContent)) {
+ assertThat(compatVersion.install(inputStream, existingVersion)).isTrue();
+ }
+
+ assertThat(readAsString(logsListFile)).isEqualTo(newContent);
}
@Test
public void testCertificateTransparencyInstaller_installSuccessfully() throws IOException {
String content = "i_am_a_certificate_and_i_am_transparent";
String version = "666";
- boolean success = false;
try (InputStream inputStream = asStream(content)) {
- success = mCertificateTransparencyInstaller.install(inputStream, version);
+ assertThat(
+ mCertificateTransparencyInstaller.install(
+ TEST_VERSION, inputStream, version))
+ .isTrue();
}
- assertThat(success).isTrue();
assertThat(mTestDir.exists()).isTrue();
assertThat(mTestDir.isDirectory()).isTrue();
- assertThat(mTestSymlink.exists()).isTrue();
- assertThat(mTestSymlink.isDirectory()).isTrue();
-
- File logsDir =
- new File(mTestDir, CertificateTransparencyInstaller.LOGS_DIR_PREFIX + version);
+ CompatibilityVersion compatVersion =
+ mCertificateTransparencyInstaller.getCompatibilityVersion(TEST_VERSION);
+ File logsDir = compatVersion.getLogsDir();
assertThat(logsDir.exists()).isTrue();
assertThat(logsDir.isDirectory()).isTrue();
- assertThat(mTestSymlink.getCanonicalPath()).isEqualTo(logsDir.getCanonicalPath());
-
- File logsListFile = new File(logsDir, CertificateTransparencyInstaller.LOGS_LIST_FILE_NAME);
+ assertThat(logsDir.getAbsolutePath())
+ .startsWith(mTestDir.getAbsolutePath() + "/" + TEST_VERSION);
+ File logsListFile = compatVersion.getLogsFile();
assertThat(logsListFile.exists()).isTrue();
+ assertThat(logsListFile.getAbsolutePath()).startsWith(logsDir.getAbsolutePath());
assertThat(readAsString(logsListFile)).isEqualTo(content);
}
@Test
public void testCertificateTransparencyInstaller_versionIsAlreadyInstalled()
- throws IOException, ErrnoException {
+ throws IOException {
String existingVersion = "666";
String existingContent = "i_was_already_installed_successfully";
- File existingLogDir =
- new File(
- mTestDir,
- CertificateTransparencyInstaller.LOGS_DIR_PREFIX + existingVersion);
- assertThat(mTestDir.mkdir()).isTrue();
- assertThat(existingLogDir.mkdir()).isTrue();
- Os.symlink(existingLogDir.getCanonicalPath(), mTestSymlink.getCanonicalPath());
- File logsListFile =
- new File(existingLogDir, CertificateTransparencyInstaller.LOGS_LIST_FILE_NAME);
- logsListFile.createNewFile();
- writeToFile(logsListFile, existingContent);
- boolean success = false;
+ CompatibilityVersion compatVersion =
+ mCertificateTransparencyInstaller.getCompatibilityVersion(TEST_VERSION);
+
+ DirectoryUtils.makeDir(mTestDir);
+ try (InputStream inputStream = asStream(existingContent)) {
+ assertThat(compatVersion.install(inputStream, existingVersion)).isTrue();
+ }
try (InputStream inputStream = asStream("i_will_be_ignored")) {
- success = mCertificateTransparencyInstaller.install(inputStream, existingVersion);
+ assertThat(
+ mCertificateTransparencyInstaller.install(
+ TEST_VERSION, inputStream, existingVersion))
+ .isFalse();
}
- assertThat(success).isFalse();
- assertThat(readAsString(logsListFile)).isEqualTo(existingContent);
- }
-
- @Test
- public void testCertificateTransparencyInstaller_versionInstalledFailed()
- throws IOException, ErrnoException {
- String existingVersion = "666";
- String existingContent = "somebody_tried_to_install_me_but_failed_halfway_through";
- String newContent = "i_am_the_real_certificate";
- File existingLogDir =
- new File(
- mTestDir,
- CertificateTransparencyInstaller.LOGS_DIR_PREFIX + existingVersion);
- assertThat(mTestDir.mkdir()).isTrue();
- assertThat(existingLogDir.mkdir()).isTrue();
- File logsListFile =
- new File(existingLogDir, CertificateTransparencyInstaller.LOGS_LIST_FILE_NAME);
- logsListFile.createNewFile();
- writeToFile(logsListFile, existingContent);
- boolean success = false;
-
- try (InputStream inputStream = asStream(newContent)) {
- success = mCertificateTransparencyInstaller.install(inputStream, existingVersion);
- }
-
- assertThat(success).isTrue();
- assertThat(mTestSymlink.getCanonicalPath()).isEqualTo(existingLogDir.getCanonicalPath());
- assertThat(readAsString(logsListFile)).isEqualTo(newContent);
+ assertThat(readAsString(compatVersion.getLogsFile())).isEqualTo(existingContent);
}
private static InputStream asStream(String string) throws IOException {
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
index 6bf186a..dd6ed2e 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
@@ -88,6 +88,13 @@
// Connects to the system Perfetto daemon and registers the trace handler.
static void InitPerfettoTracing();
+ // This prevents Perfetto from holding the data source lock when calling
+ // OnSetup, OnStart, or OnStop. The lock is still held by the LockedHandle
+ // returned by GetDataSourceLocked. Disabling this lock prevents a deadlock
+ // where OnStop holds this lock waiting for the poller to stop, but the poller
+ // is running the callback that is trying to acquire the lock.
+ static constexpr bool kRequiresCallbacksUnderLock = false;
+
// When isTest is true, skip non-hermetic code.
NetworkTraceHandler(bool isTest = false) : mIsTest(isTest) {}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
index e84cead..cfd8e9a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
@@ -27,6 +27,7 @@
import android.os.Looper;
import android.os.Message;
+import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.SharedLog;
import java.io.IOException;
@@ -167,9 +168,7 @@
* @return true if probing was in progress, false if this was a no-op
*/
public boolean stop(int id) {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException("stop can only be called from the looper thread");
- }
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
// Since this is run on the looper thread, messages cannot be currently processing and are
// all in the handler queue; unless this method is called from a message, but the current
// message cannot be cancelled.
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 4f01599..a43486e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -59,6 +59,7 @@
public class MdnsServiceTypeClient {
private static final String TAG = MdnsServiceTypeClient.class.getSimpleName();
+ private static final boolean DBG = MdnsDiscoveryManager.DBG;
@VisibleForTesting
static final int EVENT_START_QUERYTASK = 1;
static final int EVENT_QUERY_RESULT = 2;
@@ -184,10 +185,14 @@
searchOptions.numOfQueriesBeforeBackoff(),
false /* forceEnableBackoff */
);
+ final long timeToNextTaskMs = calculateTimeToNextTask(args, now);
+ sharedLog.log(String.format("Query sent with transactionId: %d. "
+ + "Next run: sessionId: %d, in %d ms",
+ sentResult.transactionId, args.sessionId, timeToNextTaskMs));
dependencies.sendMessageDelayed(
handler,
handler.obtainMessage(EVENT_START_QUERYTASK, args),
- calculateTimeToNextTask(args, now, sharedLog));
+ timeToNextTaskMs);
break;
}
default:
@@ -369,10 +374,13 @@
searchOptions.numOfQueriesBeforeBackoff(),
forceEnableBackoff
);
+ final long timeToNextTaskMs = calculateTimeToNextTask(args, now);
+ sharedLog.log(String.format("Schedule a query. Next run: sessionId: %d, in %d ms",
+ args.sessionId, timeToNextTaskMs));
dependencies.sendMessageDelayed(
handler,
handler.obtainMessage(EVENT_START_QUERYTASK, args),
- calculateTimeToNextTask(args, now, sharedLog));
+ timeToNextTaskMs);
} else {
final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
final QueryTask queryTask = new QueryTask(
@@ -492,6 +500,10 @@
// If the response is not modified and already in the cache. The cache will
// need to be updated to refresh the last receipt time.
serviceCache.addOrUpdateService(cacheKey, response);
+ if (DBG) {
+ sharedLog.v("Update the last receipt time for service:"
+ + serviceInstanceName);
+ }
}
}
if (dependencies.hasMessages(handler, EVENT_START_QUERYTASK)) {
@@ -503,10 +515,13 @@
searchOptions.numOfQueriesBeforeBackoff());
if (args != null) {
removeScheduledTask();
+ final long timeToNextTaskMs = calculateTimeToNextTask(args, now);
+ sharedLog.log(String.format("Reschedule a query. Next run: sessionId: %d, in %d ms",
+ args.sessionId, timeToNextTaskMs));
dependencies.sendMessageDelayed(
handler,
handler.obtainMessage(EVENT_START_QUERYTASK, args),
- calculateTimeToNextTask(args, now, sharedLog));
+ timeToNextTaskMs);
}
}
}
@@ -757,11 +772,8 @@
}
private static long calculateTimeToNextTask(MdnsQueryScheduler.ScheduledQueryTaskArgs args,
- long now, SharedLog sharedLog) {
- long timeToNextTasksWithBackoffInMs = Math.max(args.timeToRun - now, 0);
- sharedLog.log(String.format("Next run: sessionId: %d, in %d ms",
- args.sessionId, timeToNextTasksWithBackoffInMs));
- return timeToNextTasksWithBackoffInMs;
+ long now) {
+ return Math.max(args.timeToRun - now, 0);
}
/**
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 71f289e..67d0891 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -49,6 +49,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.SharedLog;
@@ -302,11 +303,7 @@
}
private void ensureRunningOnEthernetServiceThread() {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on EthernetService thread: "
- + Thread.currentThread().getName());
- }
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
}
/**
diff --git a/service/Android.bp b/service/Android.bp
index 94061a4..567c079 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -90,6 +90,7 @@
static_libs: [
"libnet_utils_device_common_bpfjni",
"libnet_utils_device_common_bpfutils",
+ "libnet_utils_device_common_timerfdjni",
],
shared_libs: [
"liblog",
@@ -310,7 +311,7 @@
apex_available: ["com.android.tethering"],
}
-genrule {
+java_genrule {
name: "connectivity-jarjar-rules",
defaults: ["jarjar-rules-combine-defaults"],
srcs: [
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a04ebdd..e503312 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -6003,12 +6003,10 @@
// TODO : The only way out of this is to diff old defaults and new defaults, and only
// remove ranges for those requests that won't have a replacement
final NetworkAgentInfo satisfier = nri.getSatisfier();
- if (null != satisfier && !satisfier.isDestroyed()) {
+ if (null != satisfier) {
try {
- mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
- satisfier.network.getNetId(),
- toUidRangeStableParcels(nri.getUids()),
- nri.getPreferenceOrderForNetd()));
+ modifyNetworkUidRanges(false /* add */, satisfier, nri.getUids(),
+ nri.getPreferenceOrderForNetd());
} catch (RemoteException e) {
loge("Exception setting network preference default network", e);
}
@@ -9122,11 +9120,7 @@
}
private void ensureRunningOnConnectivityServiceThread() {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on ConnectivityService thread: "
- + Thread.currentThread().getName());
- }
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
}
@VisibleForTesting
@@ -10271,8 +10265,7 @@
return stableRanges;
}
- private void maybeCloseSockets(NetworkAgentInfo nai, Set<UidRange> ranges,
- UidRangeParcel[] uidRangeParcels, int[] exemptUids) {
+ private void maybeCloseSockets(NetworkAgentInfo nai, Set<UidRange> ranges, int[] exemptUids) {
if (nai.isVPN() && !nai.networkAgentConfig.allowBypass) {
try {
if (mDeps.isAtLeastU()) {
@@ -10282,7 +10275,7 @@
}
mDeps.destroyLiveTcpSockets(UidRange.toIntRanges(ranges), exemptUidSet);
} else {
- mNetd.socketDestroy(uidRangeParcels, exemptUids);
+ mNetd.socketDestroy(toUidRangeStableParcels(ranges), exemptUids);
}
} catch (Exception e) {
loge("Exception in socket destroy: ", e);
@@ -10290,6 +10283,28 @@
}
}
+ private void modifyNetworkUidRanges(boolean add, NetworkAgentInfo nai, UidRangeParcel[] ranges,
+ int preference) throws RemoteException {
+ // UID ranges can be added or removed to a network that has already been destroyed (e.g., if
+ // the network disconnects, or a a multilayer request is filed after
+ // unregisterAfterReplacement is called).
+ if (nai.isDestroyed()) {
+ return;
+ }
+ final NativeUidRangeConfig config = new NativeUidRangeConfig(nai.network.netId,
+ ranges, preference);
+ if (add) {
+ mNetd.networkAddUidRangesParcel(config);
+ } else {
+ mNetd.networkRemoveUidRangesParcel(config);
+ }
+ }
+
+ private void modifyNetworkUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges,
+ int preference) throws RemoteException {
+ modifyNetworkUidRanges(add, nai, toUidRangeStableParcels(uidRanges), preference);
+ }
+
private void updateVpnUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges) {
int[] exemptUids = new int[2];
// TODO: Excluding VPN_UID is necessary in order to not to kill the TCP connection used
@@ -10297,24 +10312,17 @@
// starting a legacy VPN, and remove VPN_UID here. (b/176542831)
exemptUids[0] = VPN_UID;
exemptUids[1] = nai.networkCapabilities.getOwnerUid();
- UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges);
// Close sockets before modifying uid ranges so that RST packets can reach to the server.
- maybeCloseSockets(nai, uidRanges, ranges, exemptUids);
+ maybeCloseSockets(nai, uidRanges, exemptUids);
try {
- if (add) {
- mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
- nai.network.netId, ranges, PREFERENCE_ORDER_VPN));
- } else {
- mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
- nai.network.netId, ranges, PREFERENCE_ORDER_VPN));
- }
+ modifyNetworkUidRanges(add, nai, uidRanges, PREFERENCE_ORDER_VPN);
} catch (Exception e) {
loge("Exception while " + (add ? "adding" : "removing") + " uid ranges " + uidRanges +
" on netId " + nai.network.netId + ". " + e);
}
// Close sockets that established connection while requesting netd.
- maybeCloseSockets(nai, uidRanges, ranges, exemptUids);
+ maybeCloseSockets(nai, uidRanges, exemptUids);
}
private boolean isProxySetOnAnyDefaultNetwork() {
@@ -10428,16 +10436,12 @@
toAdd.removeAll(prevUids);
try {
if (!toAdd.isEmpty()) {
- mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
- nai.network.netId,
- intsToUidRangeStableParcels(toAdd),
- PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+ modifyNetworkUidRanges(true /* add */, nai, intsToUidRangeStableParcels(toAdd),
+ PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT);
}
if (!toRemove.isEmpty()) {
- mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
- nai.network.netId,
- intsToUidRangeStableParcels(toRemove),
- PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+ modifyNetworkUidRanges(false /* add */, nai, intsToUidRangeStableParcels(toRemove),
+ PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT);
}
} catch (ServiceSpecificException e) {
// Has the interface disappeared since the network was built ?
@@ -10792,16 +10796,12 @@
+ " any applications to set as the default." + nri);
}
if (null != newDefaultNetwork) {
- mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
- newDefaultNetwork.network.getNetId(),
- toUidRangeStableParcels(nri.getUids()),
- nri.getPreferenceOrderForNetd()));
+ modifyNetworkUidRanges(true /* add */, newDefaultNetwork, nri.getUids(),
+ nri.getPreferenceOrderForNetd());
}
if (null != oldDefaultNetwork) {
- mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
- oldDefaultNetwork.network.getNetId(),
- toUidRangeStableParcels(nri.getUids()),
- nri.getPreferenceOrderForNetd()));
+ modifyNetworkUidRanges(false /* add */, oldDefaultNetwork, nri.getUids(),
+ nri.getPreferenceOrderForNetd());
}
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception setting app default network", e);
@@ -13051,10 +13051,7 @@
}
private void ensureRunningOnConnectivityServiceThread() {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException("Not running on ConnectivityService thread: "
- + Thread.currentThread().getName());
- }
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
}
/**
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index 31108fc..c7d96de 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -25,6 +25,7 @@
import static android.system.OsConstants.SOL_SOCKET;
import static android.system.OsConstants.SO_SNDTIMEO;
+import static com.android.net.module.util.HandlerUtils.ensureRunningOnHandlerThread;
import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
import android.annotation.IntDef;
@@ -440,7 +441,7 @@
*/
@Nullable
public AutomaticOnOffKeepalive getKeepaliveForBinder(@NonNull final IBinder token) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
return CollectionUtils.findFirst(mAutomaticOnOffKeepalives,
it -> it.mCallback.asBinder().equals(token));
@@ -580,7 +581,7 @@
}
private void cleanupAutoOnOffKeepalive(@NonNull final AutomaticOnOffKeepalive autoKi) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
mKeepaliveStatsTracker.onStopKeepalive(autoKi.getNetwork(), autoKi.mKi.getSlot());
autoKi.close();
if (null != autoKi.mAlarmListener) mAlarmManager.cancel(autoKi.mAlarmListener);
@@ -693,7 +694,7 @@
* This should be only be called in ConnectivityService handler thread.
*/
public void dump(IndentingPrintWriter pw) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
mKeepaliveTracker.dump(pw);
// Reading DeviceConfig will check if the calling uid and calling package name are the same.
// Clear calling identity to align the calling uid and package so that it won't fail if cts
@@ -771,7 +772,7 @@
private boolean isAnyTcpSocketConnectedForFamily(FileDescriptor fd, int family, int networkMark,
int networkMask)
throws ErrnoException, InterruptedIOException {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
// Build SocketDiag messages and cache it.
if (mSockDiagMsg.get(family) == null) {
mSockDiagMsg.put(family, InetDiagMessage.buildInetDiagReqForAliveTcpSockets(family));
@@ -843,13 +844,6 @@
return mark;
}
- private void ensureRunningOnHandlerThread() {
- if (mConnectivityServiceHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on handler thread: " + Thread.currentThread().getName());
- }
- }
-
private long getTcpPollingIntervalMs(@NonNull AutomaticOnOffKeepalive ki) {
final boolean useLowTimer = mTestLowTcpPollingTimerUntilMs > System.currentTimeMillis();
// Adjust the polling interval to be smaller than the keepalive delay to preserve
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index f5fa4fb..14a935f 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -19,6 +19,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static com.android.net.module.util.HandlerUtils.ensureRunningOnHandlerThread;
import static com.android.server.connectivity.ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK;
import android.annotation.NonNull;
@@ -168,7 +169,7 @@
private void simConfigChanged() {
// If mRequestRestrictedWifiEnabled is false, constructor calls simConfigChanged
if (mRequestRestrictedWifiEnabled) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mHandler);
}
synchronized (mLock) {
unregisterCarrierPrivilegesListeners();
@@ -212,7 +213,7 @@
public void onCarrierPrivilegesChanged(
@NonNull List<String> privilegedPackageNames,
@NonNull int[] privilegedUids) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mHandler);
if (mUseCallbacksForServiceChanged) return;
// Re-trigger the synchronous check (which is also very cheap due
// to caching in CarrierPrivilegesTracker). This allows consistency
@@ -223,7 +224,7 @@
@Override
public void onCarrierServiceChanged(@Nullable final String carrierServicePackageName,
final int carrierServiceUid) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mHandler);
if (!mUseCallbacksForServiceChanged) {
// Re-trigger the synchronous check (which is also very cheap due
// to caching in CarrierPrivilegesTracker). This allows consistency
@@ -465,13 +466,6 @@
}
}
- private void ensureRunningOnHandlerThread() {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on handler thread: " + Thread.currentThread().getName());
- }
- }
-
public void dump(IndentingPrintWriter pw) {
pw.println("CarrierPrivilegeAuthenticator:");
pw.println("mRequestRestrictedWifiEnabled = " + mRequestRestrictedWifiEnabled);
diff --git a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
index 21dbb45..8acd1c8 100644
--- a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
@@ -18,6 +18,8 @@
import static android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import static com.android.net.module.util.HandlerUtils.ensureRunningOnHandlerThread;
+
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -466,7 +468,7 @@
int intervalSeconds,
int appUid,
boolean isAutoKeepalive) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
if (!isEnabled()) return;
final int keepaliveId = getKeepaliveId(network, slot);
if (keepaliveId == INVALID_KEEPALIVE_ID) return;
@@ -538,21 +540,21 @@
/** Inform the KeepaliveStatsTracker a keepalive has just been paused. */
public void onPauseKeepalive(@NonNull Network network, int slot) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
if (!isEnabled()) return;
onKeepaliveActive(network, slot, /* keepaliveActive= */ false);
}
/** Inform the KeepaliveStatsTracker a keepalive has just been resumed. */
public void onResumeKeepalive(@NonNull Network network, int slot) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
if (!isEnabled()) return;
onKeepaliveActive(network, slot, /* keepaliveActive= */ true);
}
/** Inform the KeepaliveStatsTracker a keepalive has just been stopped. */
public void onStopKeepalive(@NonNull Network network, int slot) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
if (!isEnabled()) return;
final int keepaliveId = getKeepaliveId(network, slot);
@@ -615,7 +617,7 @@
*/
@VisibleForTesting
public @NonNull DailykeepaliveInfoReported buildKeepaliveMetrics() {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
final long timeNow = mDependencies.getElapsedRealtime();
return buildKeepaliveMetrics(timeNow);
}
@@ -673,7 +675,7 @@
*/
@VisibleForTesting
public @NonNull DailykeepaliveInfoReported buildAndResetMetrics() {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
final long timeNow = mDependencies.getElapsedRealtime();
final DailykeepaliveInfoReported metrics = buildKeepaliveMetrics(timeNow);
@@ -750,7 +752,7 @@
/** Writes the stored metrics to ConnectivityStatsLog and resets. */
public void writeAndResetMetrics() {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
// Keepalive stats use repeated atoms, which are only supported on T+. If written to statsd
// on S- they will bootloop the system, so they must not be sent on S-. See b/289471411.
if (!SdkLevel.isAtLeastT()) {
@@ -771,17 +773,10 @@
/** Dump KeepaliveStatsTracker state. */
public void dump(IndentingPrintWriter pw) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mConnectivityServiceHandler);
pw.println("KeepaliveStatsTracker enabled: " + isEnabled());
pw.increaseIndent();
pw.println(buildKeepaliveMetrics().toString());
pw.decreaseIndent();
}
-
- private void ensureRunningOnHandlerThread() {
- if (mConnectivityServiceHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on handler thread: " + Thread.currentThread().getName());
- }
- }
}
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index a979681..37aef22 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -20,6 +20,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static com.android.net.module.util.CollectionUtils.contains;
+import static com.android.net.module.util.HandlerUtils.ensureRunningOnHandlerThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -500,7 +501,7 @@
// Once this code is converted to StateMachine, it will be possible to use deferMessage to
// ensure it stays in STARTING state until the interfaceLinkStateChanged notification fires,
// and possibly use a timeout (or provide some guarantees at the lower layer) to address #1.
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mNetwork.handler());
if (!isStarting() || !up || !Objects.equals(mIface, iface)) {
return;
}
@@ -524,7 +525,7 @@
* Must be called on the handler thread.
*/
public void handleInterfaceRemoved(String iface) {
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mNetwork.handler());
if (!Objects.equals(mIface, iface)) {
return;
}
@@ -546,7 +547,7 @@
@Nullable
public Inet6Address translateV4toV6(@NonNull Inet4Address addr) {
// Variables in Nat464Xlat should only be accessed from handler thread.
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mNetwork.handler());
if (!isStarted()) return null;
return convertv4ToClatv6(mNat64PrefixInUse, addr);
@@ -574,7 +575,7 @@
@Nullable
public Inet6Address getClatv6SrcAddress() {
// Variables in Nat464Xlat should only be accessed from handler thread.
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mNetwork.handler());
return mIPv6Address;
}
@@ -585,7 +586,7 @@
@Nullable
public Inet4Address getClatv4SrcAddress() {
// Variables in Nat464Xlat should only be accessed from handler thread.
- ensureRunningOnHandlerThread();
+ ensureRunningOnHandlerThread(mNetwork.handler());
if (!isStarted()) return null;
final LinkAddress v4Addr = getLinkAddress(mIface);
@@ -594,13 +595,6 @@
return (Inet4Address) v4Addr.getAddress();
}
- private void ensureRunningOnHandlerThread() {
- if (mNetwork.handler().getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on handler thread: " + Thread.currentThread().getName());
- }
- }
-
/**
* Dump the NAT64 xlat information.
*
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 76993a6..94b655f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -68,6 +68,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.WakeupMessage;
+import com.android.net.module.util.HandlerUtils;
import com.android.server.ConnectivityService;
import java.io.PrintWriter;
@@ -1138,11 +1139,7 @@
* already present.
*/
public boolean addRequest(NetworkRequest networkRequest) {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on ConnectivityService thread: "
- + Thread.currentThread().getName());
- }
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
NetworkRequest existing = mNetworkRequests.get(networkRequest.requestId);
if (existing == networkRequest) return false;
if (existing != null) {
@@ -1161,11 +1158,7 @@
* Remove the specified request from this network.
*/
public void removeRequest(int requestId) {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on ConnectivityService thread: "
- + Thread.currentThread().getName());
- }
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
NetworkRequest existing = mNetworkRequests.get(requestId);
if (existing == null) return;
updateRequestCounts(REMOVE, existing);
@@ -1187,11 +1180,7 @@
* network.
*/
public NetworkRequest requestAt(int index) {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on ConnectivityService thread: "
- + Thread.currentThread().getName());
- }
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
return mNetworkRequests.valueAt(index);
}
@@ -1222,11 +1211,7 @@
* Returns the number of requests of any type currently satisfied by this network.
*/
public int numNetworkRequests() {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on ConnectivityService thread: "
- + Thread.currentThread().getName());
- }
+ HandlerUtils.ensureRunningOnHandlerThread(mHandler);
return mNetworkRequests.size();
}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 66e1dad..a825b87 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -626,6 +626,31 @@
visibility: ["//visibility:private"],
}
+// Filegroup to build lib used by IPsec/IKE framework
+// Any class here *must* have a corresponding jarjar rule in the IPsec build rules.
+filegroup {
+ name: "net-utils-framework-ipsec-common-srcs",
+ srcs: [
+ "framework/com/android/net/module/util/HexDump.java",
+ ],
+ path: "framework",
+ visibility: ["//visibility:private"],
+}
+
+java_library {
+ name: "net-utils-framework-ipsec",
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ srcs: [":net-utils-framework-ipsec-common-srcs"],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
+ visibility: [
+ "//packages/modules/IPsec",
+ ],
+ apex_available: ["com.android.ipsec"],
+}
+
// Use a file group containing classes necessary for framework-connectivity. The file group should
// be as small as possible because because the classes end up in the bootclasspath and R8 is not
// used to remove unused classes.
diff --git a/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl b/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
index 097824f..7688e6a 100644
--- a/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
+++ b/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
@@ -115,20 +115,27 @@
void maybeRemoveDeprecatedUpstreams();
/**
- * Request an IPv4 address for the downstream.
+ * Request an IPv4 address for the downstream. Return the last time used address for the
+ * provided (interfaceType, scope) pair if possible.
*
* @param interfaceType the Tethering type (see TetheringManager#TETHERING_*).
* @param scope CONNECTIVITY_SCOPE_GLOBAL or CONNECTIVITY_SCOPE_LOCAL
- * @param useLastAddress whether to use the last address
* @param request a {@link IIpv4PrefixRequest} to report conflicts
* @return an IPv4 address allocated for the downstream, could be null
*/
@nullable
- LinkAddress requestDownstreamAddress(
+ LinkAddress requestStickyDownstreamAddress(
in int interfaceType,
in int scope,
- in boolean useLastAddress,
in IIpv4PrefixRequest request);
+ /**
+ * Request an IPv4 address for the downstream.
+ *
+ * @param request a {@link IIpv4PrefixRequest} to report conflicts
+ * @return an IPv4 address allocated for the downstream, could be null
+ */
+ @nullable
+ LinkAddress requestDownstreamAddress(in IIpv4PrefixRequest request);
/** Release the IPv4 address allocated for the downstream. */
void releaseDownstream(in IIpv4PrefixRequest request);
diff --git a/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java b/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java
index 990358d..7fcbd4e 100644
--- a/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java
+++ b/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java
@@ -78,7 +78,7 @@
private final ArrayMap<Network, List<IpPrefix>> mUpstreamPrefixMap;
// The downstreams are indexed by Ipv4PrefixRequest, which is a wrapper of the Binder object of
// IIpv4PrefixRequest.
- private final ArrayMap<Ipv4PrefixRequest, Downstream> mDownstreams;
+ private final ArrayMap<Ipv4PrefixRequest, LinkAddress> mDownstreams;
private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
private static final String LEGACY_BLUETOOTH_IFACE_ADDRESS = "192.168.44.1/24";
private final List<IpPrefix> mTetheringPrefixes;
@@ -168,10 +168,10 @@
}
private void handleMaybePrefixConflict(final List<IpPrefix> prefixes) {
- for (Map.Entry<Ipv4PrefixRequest, Downstream> entry : mDownstreams.entrySet()) {
+ for (Map.Entry<Ipv4PrefixRequest, LinkAddress> entry : mDownstreams.entrySet()) {
final Ipv4PrefixRequest request = entry.getKey();
- final Downstream downstream = entry.getValue();
- final IpPrefix target = asIpPrefix(downstream.getAddress());
+ final LinkAddress downstream = entry.getValue();
+ final IpPrefix target = asIpPrefix(downstream);
for (IpPrefix source : prefixes) {
if (isConflictPrefix(source, target)) {
@@ -209,12 +209,14 @@
// TetheringRequest has been set a static IPv4 address.
/**
- * Pick a random available address and mark its prefix as in use for the provided IpServer,
- * returns null if there is no available address.
+ * Request a downstream address for the provided IIpv4PrefixRequest.
+ *
+ * This method will first try to return the last time used address for the provided
+ * (interfaceType, scope) pair if possible. If not, it will pick a random available address and
+ * mark its prefix as in use for the provided IIpv4PrefixRequest.
*/
@Nullable
- public LinkAddress requestDownstreamAddress(int interfaceType, final int scope,
- boolean useLastAddress,
+ public LinkAddress requestStickyDownstreamAddress(int interfaceType, final int scope,
IIpv4PrefixRequest request) {
final Ipv4PrefixRequest wrappedRequest = new Ipv4PrefixRequest(request);
final AddressKey addrKey = new AddressKey(interfaceType, scope);
@@ -222,20 +224,32 @@
// Once tethering could support multiple interface with the same type,
// TetheringSoftApCallback would need to handle it among others.
final LinkAddress cachedAddress = mCachedAddresses.get(addrKey);
- if (useLastAddress && cachedAddress != null
- && !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
- mDownstreams.put(wrappedRequest, new Downstream(interfaceType, cachedAddress));
+ if (cachedAddress != null && !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
+ mDownstreams.put(wrappedRequest, cachedAddress);
return cachedAddress;
}
+ final LinkAddress newAddress = requestDownstreamAddress(request);
+ if (newAddress != null) {
+ mCachedAddresses.put(addrKey, newAddress);
+ }
+ return newAddress;
+ }
+
+ /**
+ * Pick a random available address and mark its prefix as in use for the provided
+ * IIpv4PrefixRequest. Return null if there is no available address.
+ */
+ @Nullable
+ public LinkAddress requestDownstreamAddress(IIpv4PrefixRequest request) {
+ final Ipv4PrefixRequest wrappedRequest = new Ipv4PrefixRequest(request);
final int prefixIndex = getRandomPrefixIndex();
for (int i = 0; i < mTetheringPrefixes.size(); i++) {
final IpPrefix prefixRange = mTetheringPrefixes.get(
(prefixIndex + i) % mTetheringPrefixes.size());
final LinkAddress newAddress = chooseDownstreamAddress(prefixRange);
if (newAddress != null) {
- mDownstreams.put(wrappedRequest, new Downstream(interfaceType, newAddress));
- mCachedAddresses.put(addrKey, newAddress);
+ mDownstreams.put(wrappedRequest, newAddress);
return newAddress;
}
}
@@ -379,8 +393,8 @@
// IpServer may use manually-defined address (mStaticIpv4ServerAddr) which does not include
// in mCachedAddresses.
- for (Downstream downstream : mDownstreams.values()) {
- final IpPrefix target = asIpPrefix(downstream.getAddress());
+ for (LinkAddress downstream : mDownstreams.values()) {
+ final IpPrefix target = asIpPrefix(downstream);
if (isConflictPrefix(prefix, target)) return target;
}
@@ -417,24 +431,6 @@
}
}
- private static final class Downstream {
- private final int mInterfaceType;
- private final LinkAddress mAddress;
-
- private Downstream(int interfaceType, LinkAddress address) {
- mInterfaceType = interfaceType;
- mAddress = address;
- }
-
- public int getInterfaceType() {
- return mInterfaceType;
- }
-
- public LinkAddress getAddress() {
- return mAddress;
- }
- }
-
private static class AddressKey {
private final int mTetheringType;
private final int mScope;
@@ -481,8 +477,8 @@
pw.println("mDownstreams:");
pw.increaseIndent();
- for (Downstream downstream : mDownstreams.values()) {
- pw.println(downstream.getInterfaceType() + " - " + downstream.getAddress());
+ for (LinkAddress downstream : mDownstreams.values()) {
+ pw.println(downstream);
}
pw.decreaseIndent();
diff --git a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
index 9ea0947..f5af30c 100644
--- a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
+++ b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
@@ -195,23 +195,35 @@
}
/**
- * Request an IPv4 address for the downstream.
+ * Request an IPv4 address for the downstream. Return the last time used address for the
+ * provided (interfaceType, scope) pair if possible.
*
* @param interfaceType the Tethering type (see TetheringManager#TETHERING_*).
* @param scope CONNECTIVITY_SCOPE_GLOBAL or CONNECTIVITY_SCOPE_LOCAL
- * @param useLastAddress whether to use the last address
* @param request a {@link IIpv4PrefixRequest} to report conflicts
* @return an IPv4 address allocated for the downstream, could be null
*/
@Nullable
- public LinkAddress requestDownstreamAddress(
+ public LinkAddress requestStickyDownstreamAddress(
int interfaceType,
int scope,
- boolean useLastAddress,
IIpv4PrefixRequest request) {
try {
- return mService.requestDownstreamAddress(
- interfaceType, scope, useLastAddress, request);
+ return mService.requestStickyDownstreamAddress(interfaceType, scope, request);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request an IPv4 address for the downstream.
+ *
+ * @param request a {@link IIpv4PrefixRequest} to report conflicts
+ * @return an IPv4 address allocated for the downstream, could be null
+ */
+ public LinkAddress requestDownstreamAddress(IIpv4PrefixRequest request) {
+ try {
+ return mService.requestDownstreamAddress(request);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
index d16c234..51eb47c 100644
--- a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
+++ b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
@@ -284,23 +284,40 @@
}
/**
- * Request an IPv4 address for the downstream.
+ * Request an IPv4 address for the downstream. Return the last time used address for the
+ * provided (interfaceType, scope) pair if possible.
*
* @param interfaceType the Tethering type (see TetheringManager#TETHERING_*).
* @param scope CONNECTIVITY_SCOPE_GLOBAL or CONNECTIVITY_SCOPE_LOCAL
- * @param useLastAddress whether to use the last address
* @param request a {@link IIpv4PrefixRequest} to report conflicts
* @return an IPv4 address allocated for the downstream, could be null
*/
@Override
- public LinkAddress requestDownstreamAddress(int interfaceType, int scope,
- boolean useLastAddress, IIpv4PrefixRequest request) {
+ public LinkAddress requestStickyDownstreamAddress(int interfaceType, int scope,
+ IIpv4PrefixRequest request) {
Objects.requireNonNull(request);
return BinderUtils.withCleanCallingIdentity(
() -> {
synchronized (mPrivateAddressCoordinatorLock) {
- return mPrivateAddressCoordinator.requestDownstreamAddress(
- interfaceType, scope, useLastAddress, request);
+ return mPrivateAddressCoordinator.requestStickyDownstreamAddress(
+ interfaceType, scope, request);
+ }
+ });
+ }
+
+ /**
+ * Request an IPv4 address for the downstream.
+ *
+ * @param request a {@link IIpv4PrefixRequest} to report conflicts
+ * @return an IPv4 address allocated for the downstream, could be null
+ */
+ @Override
+ public LinkAddress requestDownstreamAddress(IIpv4PrefixRequest request) {
+ Objects.requireNonNull(request);
+ return BinderUtils.withCleanCallingIdentity(
+ () -> {
+ synchronized (mPrivateAddressCoordinatorLock) {
+ return mPrivateAddressCoordinator.requestDownstreamAddress(request);
}
});
}
diff --git a/staticlibs/native/timerfdutils/Android.bp b/staticlibs/native/timerfdutils/Android.bp
new file mode 100644
index 0000000..939a2d2
--- /dev/null
+++ b/staticlibs/native/timerfdutils/Android.bp
@@ -0,0 +1,46 @@
+// 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 {
+ default_team: "trendy_team_fwk_core_networking",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libnet_utils_device_common_timerfdjni",
+ srcs: [
+ "com_android_net_module_util_TimerFdUtils.cpp",
+ ],
+ header_libs: [
+ "jni_headers",
+ ],
+ shared_libs: [
+ "liblog",
+ "libnativehelper_compat_libc++",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+ sdk_version: "current",
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
+ "//apex_available:platform",
+ ],
+ visibility: [
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+}
diff --git a/staticlibs/native/timerfdutils/com_android_net_module_util_TimerFdUtils.cpp b/staticlibs/native/timerfdutils/com_android_net_module_util_TimerFdUtils.cpp
new file mode 100644
index 0000000..c4c960d
--- /dev/null
+++ b/staticlibs/native/timerfdutils/com_android_net_module_util_TimerFdUtils.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_utf_chars.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MSEC_PER_SEC 1000
+#define NSEC_PER_MSEC 1000000
+
+namespace android {
+
+static jint
+com_android_net_module_util_TimerFdUtils_createTimerFd(JNIEnv *env,
+ jclass clazz) {
+ int tfd;
+ tfd = timerfd_create(CLOCK_BOOTTIME, 0);
+ if (tfd == -1) {
+ jniThrowErrnoException(env, "createTimerFd", tfd);
+ }
+ return tfd;
+}
+
+static void
+com_android_net_module_util_TimerFdUtils_setTime(JNIEnv *env, jclass clazz,
+ jint tfd, jlong milliseconds) {
+ struct itimerspec new_value;
+ new_value.it_value.tv_sec = milliseconds / MSEC_PER_SEC;
+ new_value.it_value.tv_nsec = (milliseconds % MSEC_PER_SEC) * NSEC_PER_MSEC;
+ // Set the interval time to 0 because it's designed for repeated timer expirations after the
+ // initial expiration, which doesn't fit the current usage.
+ new_value.it_interval.tv_sec = 0;
+ new_value.it_interval.tv_nsec = 0;
+
+ int ret = timerfd_settime(tfd, 0, &new_value, NULL);
+ if (ret == -1) {
+ jniThrowErrnoException(env, "setTime", ret);
+ }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"createTimerFd", "()I",
+ (void *)com_android_net_module_util_TimerFdUtils_createTimerFd},
+ {"setTime", "(IJ)V",
+ (void *)com_android_net_module_util_TimerFdUtils_setTime},
+};
+
+int register_com_android_net_module_util_TimerFdUtils(JNIEnv *env,
+ char const *class_name) {
+ return jniRegisterNativeMethods(env, class_name, gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/staticlibs/tests/unit/host/python/assert_utils_test.py b/staticlibs/tests/unit/host/python/assert_utils_test.py
index 7a33373..1d85a12 100644
--- a/staticlibs/tests/unit/host/python/assert_utils_test.py
+++ b/staticlibs/tests/unit/host/python/assert_utils_test.py
@@ -14,7 +14,9 @@
from mobly import asserts
from mobly import base_test
-from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError, expect_with_retry
+from net_tests_utils.host.python.assert_utils import (
+ UnexpectedBehaviorError, UnexpectedExceptionError, expect_with_retry, expect_throws
+)
class TestAssertUtils(base_test.BaseTestClass):
@@ -92,3 +94,22 @@
retry_interval_sec=0,
)
asserts.assert_true(retry_action_called, "retry_action not called.")
+
+ def test_expect_exception_throws(self):
+ def raise_unexpected_behavior_error():
+ raise UnexpectedBehaviorError()
+
+ expect_throws(raise_unexpected_behavior_error, UnexpectedBehaviorError)
+
+ def test_unexpect_exception_throws(self):
+ def raise_value_error():
+ raise ValueError()
+
+ with asserts.assert_raises(UnexpectedExceptionError):
+ expect_throws(raise_value_error, UnexpectedBehaviorError)
+
+ def test_no_exception_throws(self):
+ def raise_no_error():
+ return
+
+ expect_throws(raise_no_error, UnexpectedBehaviorError)
\ No newline at end of file
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 13e1dc0..f4ed9e4 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -103,7 +103,7 @@
"mcts-wifi",
"mcts-dnsresolver",
],
- data: [":ConnectivityTestPreparer"],
+ device_common_data: [":ConnectivityTestPreparer"],
}
python_library_host {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/AutoReleaseNetworkCallbackRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/AutoReleaseNetworkCallbackRule.kt
index 93422ad..be6947f 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/AutoReleaseNetworkCallbackRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/AutoReleaseNetworkCallbackRule.kt
@@ -98,10 +98,10 @@
cellRequestCb = null
}
- private fun addCallback(
- cb: TestableNetworkCallback,
- registrar: (TestableNetworkCallback) -> Unit
- ): TestableNetworkCallback {
+ private fun <T> addCallback(
+ cb: T,
+ registrar: (NetworkCallback) -> Unit
+ ): T where T : NetworkCallback {
registrar(cb)
cbToCleanup.add(cb)
return cb
@@ -142,17 +142,24 @@
/**
* File a callback for a NetworkRequest.
*
- * This will fail tests (throw) if the cell network cannot be obtained, or if it was already
- * requested.
- *
* Tests may call [unregisterNetworkCallback] once they are done using the returned [Network],
* otherwise it will be automatically unrequested after the test.
*/
@JvmOverloads
fun registerNetworkCallback(
+ request: NetworkRequest
+ ): TestableNetworkCallback = registerNetworkCallback(request, TestableNetworkCallback())
+
+ /**
+ * File a callback for a NetworkRequest.
+ *
+ * Tests may call [unregisterNetworkCallback] once they are done using the returned [Network],
+ * otherwise it will be automatically unrequested after the test.
+ */
+ fun <T> registerNetworkCallback(
request: NetworkRequest,
- cb: TestableNetworkCallback = TestableNetworkCallback()
- ) = addCallback(cb) { cm.registerNetworkCallback(request, it) }
+ cb: T
+ ) where T : NetworkCallback = addCallback(cb) { cm.registerNetworkCallback(request, it) }
/**
* @see ConnectivityManager.registerDefaultNetworkCallback
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
index f5a5b4d..ea86281 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
@@ -16,19 +16,46 @@
package com.android.testutils
+import android.Manifest.permission.NETWORK_SETTINGS
+import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
+import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.content.pm.PackageManager.FEATURE_WIFI
import android.device.collectors.BaseMetricListener
import android.device.collectors.DataRecord
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.wifi.WifiInfo
+import android.net.wifi.WifiManager
import android.os.Build
import android.os.ParcelFileDescriptor
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.SIM_STATE_UNKNOWN
import android.util.Log
+import androidx.annotation.RequiresApi
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel.isAtLeastS
import java.io.ByteArrayOutputStream
import java.io.File
+import java.io.FileOutputStream
import java.io.PrintWriter
import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
import kotlin.test.assertNull
+import org.json.JSONObject
import org.junit.AssumptionViolatedException
import org.junit.runner.Description
+import org.junit.runner.Result
import org.junit.runner.notification.Failure
/**
@@ -52,11 +79,35 @@
var instance: ConnectivityDiagnosticsCollector? = null
}
+ private var failureHeader: String? = null
private val buffer = ByteArrayOutputStream()
private val collectorDir: File by lazy {
createAndEmptyDirectory(COLLECTOR_DIR)
}
private val outputFiles = mutableSetOf<String>()
+ private val cbHelper = NetworkCallbackHelper()
+ private val networkCallback = MonitoringNetworkCallback()
+
+ inner class MonitoringNetworkCallback : NetworkCallback() {
+ val currentMobileDataNetworks = mutableMapOf<Network, NetworkCapabilities>()
+ val currentVpnNetworks = mutableMapOf<Network, NetworkCapabilities>()
+ val currentWifiNetworks = mutableMapOf<Network, NetworkCapabilities>()
+
+ override fun onLost(network: Network) {
+ currentWifiNetworks.remove(network)
+ currentMobileDataNetworks.remove(network)
+ }
+
+ override fun onCapabilitiesChanged(network: Network, nc: NetworkCapabilities) {
+ if (nc.hasTransport(TRANSPORT_VPN)) {
+ currentVpnNetworks[network] = nc
+ } else if (nc.hasTransport(TRANSPORT_WIFI)) {
+ currentWifiNetworks[network] = nc
+ } else if (nc.hasTransport(TRANSPORT_CELLULAR)) {
+ currentMobileDataNetworks[network] = nc
+ }
+ }
+ }
override fun onSetUp() {
assertNull(instance, "ConnectivityDiagnosticsCollectors were set up multiple times")
@@ -72,6 +123,24 @@
instance = null
}
+ override fun onTestRunStart(runData: DataRecord?, description: Description?) {
+ runAsShell(NETWORK_SETTINGS) {
+ cbHelper.registerNetworkCallback(
+ NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_WIFI)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .build(), networkCallback
+ )
+ }
+ }
+
+ override fun onTestRunEnd(runData: DataRecord?, result: Result?) {
+ // onTestRunEnd is called regardless of success/failure, and the Result contains summary of
+ // run/failed/ignored... tests.
+ cbHelper.unregisterAll()
+ }
+
override fun onTestFail(testData: DataRecord, description: Description, failure: Failure) {
// TODO: find a way to disable this behavior only on local runs, to avoid slowing them down
// when iterating on failing tests.
@@ -119,12 +188,116 @@
}
val outFile = File(collectorDir, filename + FILENAME_SUFFIX)
outputFiles.add(filename)
- outFile.writeBytes(buffer.toByteArray())
+ FileOutputStream(outFile).use { fos ->
+ failureHeader?.let {
+ fos.write(it.toByteArray())
+ fos.write("\n".toByteArray())
+ }
+ fos.write(buffer.toByteArray())
+ }
+ failureHeader = null
buffer.reset()
val fileKey = "${ConnectivityDiagnosticsCollector::class.qualifiedName}_$filename"
testData.addFileMetric(fileKey, outFile)
}
+ private fun maybeCollectFailureHeader() {
+ if (failureHeader != null) {
+ Log.i(TAG, "Connectivity diagnostics failure header already collected, skipping")
+ return
+ }
+
+ val instr = InstrumentationRegistry.getInstrumentation()
+ val ctx = instr.context
+ val pm = ctx.packageManager
+ val hasWifi = pm.hasSystemFeature(FEATURE_WIFI)
+ val hasMobileData = pm.hasSystemFeature(FEATURE_TELEPHONY)
+ val tm = if (hasMobileData) ctx.getSystemService(TelephonyManager::class.java) else null
+ // getAdoptedShellPermissions is S+. Optimistically assume that tests are not holding on
+ // shell permissions during failure/cleanup on R.
+ val canUseShell = !isAtLeastS() ||
+ instr.uiAutomation.getAdoptedShellPermissions().isNullOrEmpty()
+ val headerObj = JSONObject()
+ if (canUseShell) {
+ runAsShell(READ_PRIVILEGED_PHONE_STATE, NETWORK_SETTINGS) {
+ headerObj.apply {
+ put("deviceSerial", Build.getSerial())
+ // The network callback filed on start cannot get the WifiInfo as it would need
+ // to keep NETWORK_SETTINGS permission throughout the test run. Try to
+ // obtain it while holding the permission at the end of the test.
+ val wifiInfo = networkCallback.currentWifiNetworks.keys.firstOrNull()?.let {
+ getWifiInfo(it)
+ }
+ put("ssid", wifiInfo?.ssid)
+ put("bssid", wifiInfo?.bssid)
+ put("simState", tm?.simState ?: SIM_STATE_UNKNOWN)
+ put("mccMnc", tm?.simOperator)
+ }
+ }
+ } else {
+ Log.w(TAG, "The test is still holding shell permissions, cannot collect privileged " +
+ "device info")
+ headerObj.put("shellPermissionsUnavailable", true)
+ }
+ failureHeader = headerObj.apply {
+ put("time", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now()))
+ put(
+ "wifiEnabled",
+ hasWifi && ctx.getSystemService(WifiManager::class.java).isWifiEnabled
+ )
+ put("connectedWifiCount", networkCallback.currentWifiNetworks.size)
+ put("validatedWifiCount", networkCallback.currentWifiNetworks.filterValues {
+ it.hasCapability(NET_CAPABILITY_VALIDATED)
+ }.size)
+ put("mobileDataConnectivityPossible", tm?.isDataConnectivityPossible ?: false)
+ put("connectedMobileDataCount", networkCallback.currentMobileDataNetworks.size)
+ put("validatedMobileDataCount",
+ networkCallback.currentMobileDataNetworks.filterValues {
+ it.hasCapability(NET_CAPABILITY_VALIDATED)
+ }.size
+ )
+ }.toString()
+ }
+
+ private class WifiInfoCallback : NetworkCallback {
+ private val network: Network
+ val wifiInfoFuture = CompletableFuture<WifiInfo?>()
+ constructor(network: Network) : super() {
+ this.network = network
+ }
+ @RequiresApi(Build.VERSION_CODES.S)
+ constructor(network: Network, flags: Int) : super(flags) {
+ this.network = network
+ }
+ override fun onCapabilitiesChanged(net: Network, nc: NetworkCapabilities) {
+ if (network == net) {
+ wifiInfoFuture.complete(nc.transportInfo as? WifiInfo)
+ }
+ }
+ }
+
+ private fun getWifiInfo(network: Network): WifiInfo? {
+ // Get the SSID via network callbacks, as the Networks are obtained via callbacks, and
+ // synchronous calls (CM#getNetworkCapabilities) and callbacks should not be mixed.
+ // A new callback needs to be filed and received while holding NETWORK_SETTINGS permission.
+ val cb = if (isAtLeastS()) {
+ WifiInfoCallback(network, FLAG_INCLUDE_LOCATION_INFO)
+ } else {
+ WifiInfoCallback(network)
+ }
+ cbHelper.registerNetworkCallback(
+ NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET).build(), cb)
+ return try {
+ cb.wifiInfoFuture.get(1L, TimeUnit.SECONDS)
+ } catch (e: TimeoutException) {
+ null
+ } finally {
+ cbHelper.unregisterNetworkCallback(cb)
+ }
+ }
+
/**
* Add connectivity diagnostics to the test data dump.
*
@@ -134,6 +307,7 @@
* @param exceptionContext An exception to write a stacktrace to the dump for context.
*/
fun collectTestFailureDiagnostics(exceptionContext: Throwable? = null) {
+ maybeCollectFailureHeader()
collectDumpsysConnectivity(exceptionContext)
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt
index 68248ca..785e55a 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt
@@ -89,6 +89,7 @@
} cleanupStep {
runAsShell(WRITE_DEVICE_CONFIG) {
originalConfig.forEach { (key, value) ->
+ Log.i(TAG, "Resetting config \"${key.second}\" to \"$value\"")
DeviceConfig.setProperty(
key.first, key.second, value, false /* makeDefault */)
}
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index 7fe60bd..55ac860 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -182,22 +182,23 @@
ad: android_device.AndroidDevice,
) -> bool:
- # Invoke the shell command with empty argument and see how NetworkStack respond.
- # If supported, an IllegalArgumentException with help page will be printed.
- functions_with_args = (
- # list all functions and args with (func, *args) tuple
- (start_capture_packets, (ad, "")),
- (stop_capture_packets, (ad, "")),
- (get_matched_packet_counts, (ad, "", ""))
- )
-
- for func, args in functions_with_args:
- try:
- func(*args)
- except UnsupportedOperationException:
- return False
- except Exception:
- continue
+ try:
+ # Invoke the shell command with empty argument and see how NetworkStack respond.
+ # If supported, an IllegalArgumentException with help page will be printed.
+ assert_utils.expect_throws(
+ lambda: start_capture_packets(ad, ""),
+ assert_utils.UnexpectedBehaviorError
+ )
+ assert_utils.expect_throws(
+ lambda: stop_capture_packets(ad, ""),
+ assert_utils.UnexpectedBehaviorError
+ )
+ assert_utils.expect_throws(
+ lambda: get_matched_packet_counts(ad, "", ""),
+ assert_utils.UnexpectedBehaviorError
+ )
+ except assert_utils.UnexpectedExceptionError:
+ return False
# If no UnsupportOperationException is thrown, regard it as supported
return True
diff --git a/staticlibs/testutils/host/python/assert_utils.py b/staticlibs/testutils/host/python/assert_utils.py
index da1bb9e..40094a2 100644
--- a/staticlibs/testutils/host/python/assert_utils.py
+++ b/staticlibs/testutils/host/python/assert_utils.py
@@ -19,6 +19,8 @@
class UnexpectedBehaviorError(Exception):
"""Raised when there is an unexpected behavior during applying a procedure."""
+class UnexpectedExceptionError(Exception):
+ """Raised when there is an unexpected exception throws during applying a procedure"""
def expect_with_retry(
predicate: Callable[[], bool],
@@ -41,3 +43,17 @@
raise UnexpectedBehaviorError(
"Predicate didn't become true after " + str(max_retries) + " retries."
)
+
+def expect_throws(runnable: callable, exception_class) -> None:
+ try:
+ runnable()
+ raise UnexpectedBehaviorError("Expected an exception, but none was thrown")
+ except exception_class:
+ pass
+ except UnexpectedBehaviorError as e:
+ raise e
+ except Exception as e:
+ raise UnexpectedExceptionError(
+ f"Expected exception of type {exception_class.__name__}, "
+ f"but got {type(e).__name__}: {e}"
+ )
\ No newline at end of file
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt b/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt
index 1883387..d1d5649 100644
--- a/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt
@@ -20,11 +20,13 @@
import com.android.testutils.FunctionalUtils.ThrowingRunnable
import java.lang.reflect.Modifier
+import java.util.function.BooleanSupplier
import kotlin.system.measureTimeMillis
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
+import kotlin.test.fail
private const val TAG = "Connectivity unit test"
@@ -118,4 +120,25 @@
val actualSet: HashSet<T> = HashSet(actual)
assertEquals(actualSet.size, actual.size, "actual list contains duplicates")
assertEquals(expectedSet, actualSet)
+}
+
+@JvmOverloads
+fun assertEventuallyTrue(
+ descr: String,
+ timeoutMs: Long,
+ pollIntervalMs: Long = 10L,
+ fn: BooleanSupplier
+) {
+ // This should use SystemClock.elapsedRealtime() since nanoTime does not include time in deep
+ // sleep, but this is a host-device library and SystemClock is Android-specific (not available
+ // on host). When waiting for a condition during tests the device would generally not go into
+ // deep sleep, and the polling sleep would go over the timeout anyway in that case, so this is
+ // fine.
+ val limit = System.nanoTime() + timeoutMs * 1000
+ while (!fn.asBoolean) {
+ if (System.nanoTime() > limit) {
+ fail(descr)
+ }
+ Thread.sleep(pollIntervalMs)
+ }
}
\ No newline at end of file
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 920492f..bb1009b 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -61,7 +61,7 @@
// Combine Connectivity, NetworkStack and Tethering jarjar rules for coverage target.
// The jarjar files are simply concatenated in the order specified in srcs.
// jarjar stops at the first matching rule, so order of concatenation affects the output.
-genrule {
+java_genrule {
name: "ConnectivityCoverageJarJarRules",
defaults: ["jarjar-rules-combine-defaults"],
srcs: [
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 97be91a..0ac9ce1 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -56,7 +56,7 @@
"mts-tethering",
"sts",
],
- data: [
+ device_common_data: [
":CtsHostsideNetworkTestsApp",
":CtsHostsideNetworkTestsApp2",
":CtsHostsideNetworkCapTestsAppWithoutProperty",
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index 40aa1e4..949be85 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -37,7 +37,7 @@
test_options: {
unit_test: false,
},
- data: [
+ device_common_data: [
// Package the snippet with the mobly test
":connectivity_multi_devices_snippet",
],
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index a65316f..7590a2b 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -60,7 +60,8 @@
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<!-- Pattern matching the fileKey used by ConnectivityDiagnosticsCollector when calling addFileMetric -->
- <option name="pull-pattern-keys" value="com.android.testutils.ConnectivityDiagnosticsCollector.*"/>
+ <option name="pull-pattern-keys" value="com.android.testutils.ConnectivityDiagnosticsCollector.*" />
+ <option name="log-data-type" value="CONNDIAG" />
<option name="collect-on-run-ended-only" value="true" />
</metrics_collector>
<!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 0e9ea0c..3a8252a 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -113,6 +113,7 @@
import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.MiscAsserts.assertEventuallyTrue;
import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -2934,12 +2935,7 @@
mCm.getActiveNetwork(), false /* accept */ , false /* always */));
}
- private void ensureCellIsValidatedBeforeMockingValidationUrls() {
- // Verify that current supported network is validated so that the mock http server will not
- // apply to unexpected networks. Also see aosp/2208680.
- //
- // This may also apply to wifi in principle, but in practice methods that mock validation
- // URL all disconnect wifi forcefully anyway, so don't wait for wifi to validate.
+ private void ensureCellIsValidated() {
if (mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
new ConnectUtil(mContext).ensureCellularValidated();
}
@@ -3022,9 +3018,13 @@
networkCallbackRule.requestCell();
final Network wifiNetwork = prepareUnvalidatedNetwork();
- // Default network should not be wifi ,but checking that wifi is not the default doesn't
- // guarantee that it won't become the default in the future.
- assertNotEquals(wifiNetwork, mCm.getActiveNetwork());
+ // Default network should not be wifi ,but checking that Wi-Fi is not the default
+ // doesn't guarantee that it won't become the default in the future.
+ // On U 24Q2+ telephony may teardown (unregisterAfterReplacement) its network when Wi-Fi
+ // is toggled (as part of prepareUnvalidatedNetwork here). Give some time for Wi-Fi to
+ // not be default in case telephony is reconnecting.
+ assertEventuallyTrue("Wifi remained default despite being unvalidated",
+ WIFI_CONNECT_TIMEOUT_MS, () -> !wifiNetwork.equals(mCm.getActiveNetwork()));
final TestableNetworkCallback wifiCb = networkCallbackRule.registerNetworkCallback(
makeWifiNetworkRequest());
@@ -3061,7 +3061,7 @@
try {
final Network cellNetwork = networkCallbackRule.requestCell();
- ensureCellIsValidatedBeforeMockingValidationUrls();
+ ensureCellIsValidated();
final Network wifiNetwork = prepareValidatedNetwork();
final TestableNetworkCallback defaultCb =
@@ -3157,7 +3157,12 @@
}
private Network prepareValidatedNetwork() throws Exception {
- ensureCellIsValidatedBeforeMockingValidationUrls();
+ // Verify that current supported network is validated so that the mock http server will not
+ // apply to unexpected networks. Also see aosp/2208680.
+ //
+ // This may also apply to wifi in principle, but in practice methods that mock validation
+ // URL all disconnect wifi forcefully anyway, so don't wait for wifi to validate.
+ ensureCellIsValidated();
prepareHttpServer();
configTestServer(Status.NO_CONTENT, Status.NO_CONTENT);
@@ -3169,7 +3174,7 @@
}
private Network preparePartialConnectivity() throws Exception {
- ensureCellIsValidatedBeforeMockingValidationUrls();
+ ensureCellIsValidated();
prepareHttpServer();
// Configure response code for partial connectivity
@@ -3184,7 +3189,7 @@
}
private Network prepareUnvalidatedNetwork() throws Exception {
- ensureCellIsValidatedBeforeMockingValidationUrls();
+ ensureCellIsValidated();
prepareHttpServer();
// Configure response code for unvalidated network
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 890c071..f2c6d33 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -1874,4 +1874,45 @@
},
false /* enableEncrypt */);
}
+
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Test
+ public void testMigrateWhenMultipleTunnelsExist() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelMigrateFeature());
+
+ final int spi = getRandomSpi(LOCAL_OUTER_6, REMOTE_OUTER_6);
+
+ // Create tunnelIfaceFoo and tunnelIfaceBar. Verify tunnelIfaceBar migration will not throw
+ try (IpSecManager.IpSecTunnelInterface tunnelIfaceFoo =
+ mISM.createIpSecTunnelInterface(
+ LOCAL_OUTER_4, REMOTE_OUTER_4, sTunWrapper.network)) {
+
+ buildTunnelNetworkAndRunTestsSimple(
+ spi,
+ (ipsecNetwork,
+ tunnelIfaceBar,
+ tunUtils,
+ inTunnelTransform,
+ outTunnelTransform,
+ localOuter,
+ remoteOuter,
+ seqNum) -> {
+ tunnelIfaceBar.setUnderlyingNetwork(sTunWrapperNew.network);
+
+ mISM.startTunnelModeTransformMigration(
+ inTunnelTransform, REMOTE_OUTER_6_NEW, LOCAL_OUTER_6_NEW);
+ mISM.startTunnelModeTransformMigration(
+ outTunnelTransform, LOCAL_OUTER_6_NEW, REMOTE_OUTER_6_NEW);
+
+ mISM.applyTunnelModeTransform(
+ tunnelIfaceBar, IpSecManager.DIRECTION_IN, inTunnelTransform);
+ mISM.applyTunnelModeTransform(
+ tunnelIfaceBar, IpSecManager.DIRECTION_OUT, outTunnelTransform);
+
+ return 0 /* not used */;
+ },
+ true /* enableEncrypt */);
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index 11fc6df..fef085d 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -41,6 +41,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.app.Instrumentation;
import android.app.usage.NetworkStats;
@@ -68,13 +69,16 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.SystemUtil;
import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.AutoReleaseNetworkCallbackRule;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.RecorderCallback.CallbackEntry;
+import com.android.testutils.TestableNetworkCallback;
import org.junit.After;
import org.junit.Before;
@@ -95,12 +99,18 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
-@ConnectivityModuleTest
+// TODO: Fix thread leaks in testCallback and annotating with @MonitorThreadLeak.
@AppModeFull(reason = "instant apps cannot be granted USAGE_STATS")
-@RunWith(AndroidJUnit4.class)
+@ConnectivityModuleTest
+@DevSdkIgnoreRunner.RestoreDefaultNetwork
+@RunWith(DevSdkIgnoreRunner.class)
public class NetworkStatsManagerTest {
- @Rule
+ @Rule(order = 1)
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(Build.VERSION_CODES.Q);
+ @Rule(order = 2)
+ public final AutoReleaseNetworkCallbackRule
+ networkCallbackRule = new AutoReleaseNetworkCallbackRule();
+
private static final String LOG_TAG = "NetworkStatsManagerTest";
private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
@@ -119,12 +129,19 @@
private static final long LONG_TOLERANCE = MINUTE * 120;
private abstract class NetworkInterfaceToTest {
+
+ final TestableNetworkCallback mRequestNetworkCb = new TestableNetworkCallback();
private boolean mMetered;
private boolean mRoaming;
private boolean mIsDefault;
abstract int getNetworkType();
- abstract int getTransportType();
+
+ abstract Network requestNetwork();
+
+ void unrequestNetwork() {
+ networkCallbackRule.unregisterNetworkCallback(mRequestNetworkCb);
+ }
public boolean getMetered() {
return mMetered;
@@ -151,7 +168,13 @@
}
abstract String getSystemFeature();
- abstract String getErrorMessage();
+
+ @NonNull NetworkRequest buildRequestForTransport(int transport) {
+ return new NetworkRequest.Builder()
+ .addTransportType(transport)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build();
+ }
}
private final NetworkInterfaceToTest[] mNetworkInterfacesToTest =
@@ -163,19 +186,20 @@
}
@Override
- public int getTransportType() {
- return NetworkCapabilities.TRANSPORT_WIFI;
+ public Network requestNetwork() {
+ networkCallbackRule.requestNetwork(buildRequestForTransport(
+ NetworkCapabilities.TRANSPORT_WIFI),
+ mRequestNetworkCb, TIMEOUT_MILLIS);
+ return mRequestNetworkCb.expect(CallbackEntry.AVAILABLE,
+ "Wifi network not available. "
+ + "Please ensure the device has working wifi."
+ ).getNetwork();
}
@Override
public String getSystemFeature() {
return PackageManager.FEATURE_WIFI;
}
-
- @Override
- public String getErrorMessage() {
- return " Please make sure you are connected to a WiFi access point.";
- }
},
new NetworkInterfaceToTest() {
@Override
@@ -184,22 +208,20 @@
}
@Override
- public int getTransportType() {
- return NetworkCapabilities.TRANSPORT_CELLULAR;
+ public Network requestNetwork() {
+ networkCallbackRule.requestNetwork(buildRequestForTransport(
+ NetworkCapabilities.TRANSPORT_CELLULAR),
+ mRequestNetworkCb, TIMEOUT_MILLIS);
+ return mRequestNetworkCb.expect(CallbackEntry.AVAILABLE,
+ "Cell network not available. "
+ + "Please ensure the device has working mobile data."
+ ).getNetwork();
}
@Override
public String getSystemFeature() {
return PackageManager.FEATURE_TELEPHONY;
}
-
- @Override
- public String getErrorMessage() {
- return " Please make sure you have added a SIM card with data plan to"
- + " your phone, have enabled data over cellular and in case of"
- + " dual SIM devices, have selected the right SIM "
- + "for data connection.";
- }
}
};
@@ -215,7 +237,22 @@
private String mWriteSettingsMode;
private String mUsageStatsMode;
- private void exerciseRemoteHost(Network network, URL url) throws Exception {
+ // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4,
+ // we need to wait until IPv4 is available or the test will spuriously fail.
+ private static void waitForHostResolution(@NonNull Network network, @NonNull URL url) {
+ for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) {
+ try {
+ network.getAllByName(url.getHost());
+ return;
+ } catch (UnknownHostException e) {
+ SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS);
+ }
+ }
+ fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)",
+ url.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS));
+ }
+
+ private void exerciseRemoteHost(@NonNull Network network, @NonNull URL url) throws Exception {
NetworkInfo networkInfo = mCm.getNetworkInfo(network);
if (networkInfo == null) {
Log.w(LOG_TAG, "Network info is null");
@@ -311,97 +348,44 @@
return result.contains("FOREGROUND");
}
- private class NetworkCallback extends ConnectivityManager.NetworkCallback {
- private long mTolerance;
- private URL mUrl;
- public boolean success;
- public boolean metered;
- public boolean roaming;
- public boolean isDefault;
-
- NetworkCallback(long tolerance, URL url) {
- mTolerance = tolerance;
- mUrl = url;
- success = false;
- metered = false;
- roaming = false;
- isDefault = false;
- }
-
- // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4,
- // we need to wait until IPv4 is available or the test will spuriously fail.
- private void waitForHostResolution(Network network) {
- for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) {
- try {
- network.getAllByName(mUrl.getHost());
- return;
- } catch (UnknownHostException e) {
- SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS);
- }
- }
- fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)",
- mUrl.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS));
- }
-
- @Override
- public void onAvailable(Network network) {
- try {
- mStartTime = System.currentTimeMillis() - mTolerance;
- isDefault = network.equals(mCm.getActiveNetwork());
- waitForHostResolution(network);
- exerciseRemoteHost(network, mUrl);
- mEndTime = System.currentTimeMillis() + mTolerance;
- success = true;
- metered = !mCm.getNetworkCapabilities(network)
- .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- roaming = !mCm.getNetworkCapabilities(network)
- .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
- synchronized (NetworkStatsManagerTest.this) {
- NetworkStatsManagerTest.this.notify();
- }
- } catch (Exception e) {
- Log.w(LOG_TAG, "exercising remote host failed.", e);
- success = false;
- }
- }
- }
-
private boolean shouldTestThisNetworkType(int networkTypeIndex) {
return mPm.hasSystemFeature(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
}
+ @NonNull
+ private Network requestNetworkAndSetAttributes(
+ @NonNull NetworkInterfaceToTest networkInterface) {
+ final Network network = networkInterface.requestNetwork();
+
+ // These attributes are needed when performing NetworkStats queries.
+ // Fetch caps from the first capabilities changed event since the
+ // interested attributes are not mutable, and not expected to be
+ // changed during the test.
+ final NetworkCapabilities caps = networkInterface.mRequestNetworkCb.expect(
+ CallbackEntry.NETWORK_CAPS_UPDATED, network).getCaps();
+ networkInterface.setMetered(!caps.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
+ networkInterface.setRoaming(!caps.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING));
+ networkInterface.setIsDefault(network.equals(mCm.getActiveNetwork()));
+
+ return network;
+ }
+
private void requestNetworkAndGenerateTraffic(int networkTypeIndex, final long tolerance)
throws Exception {
final NetworkInterfaceToTest networkInterface = mNetworkInterfacesToTest[networkTypeIndex];
- final NetworkCallback callback = new NetworkCallback(tolerance,
- new URL(CHECK_CONNECTIVITY_URL));
- mCm.requestNetwork(new NetworkRequest.Builder()
- .addTransportType(networkInterface.getTransportType())
- .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- .build(), callback);
- synchronized (this) {
- long now = System.currentTimeMillis();
- final long deadline = (long) (now + TIMEOUT_MILLIS * 2.4);
- while (!callback.success && now < deadline) {
- try {
- wait(deadline - now);
- } catch (InterruptedException e) {
- }
- now = System.currentTimeMillis();
- }
- }
- mCm.unregisterNetworkCallback(callback);
- if (!callback.success) {
- fail(networkInterface.getSystemFeature()
- + " is a reported system feature, however no corresponding "
- + "connected network interface was found or the attempt "
- + "to connect and read has timed out (timeout = " + (TIMEOUT_MILLIS * 2.4)
- + "ms)." + networkInterface.getErrorMessage());
- }
+ final Network network = requestNetworkAndSetAttributes(networkInterface);
- networkInterface.setMetered(callback.metered);
- networkInterface.setRoaming(callback.roaming);
- networkInterface.setIsDefault(callback.isDefault);
+ mStartTime = System.currentTimeMillis() - tolerance;
+ waitForHostResolution(network, new URL(CHECK_CONNECTIVITY_URL));
+ exerciseRemoteHost(network, new URL(CHECK_CONNECTIVITY_URL));
+ mEndTime = System.currentTimeMillis() + tolerance;
+
+ // It is fine if the test fails and this line is not reached.
+ // The AutoReleaseNetworkCallbackRule will eventually release
+ // all unwanted callbacks.
+ networkInterface.unrequestNetwork();
}
private String getSubscriberId(int networkIndex) {
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index ad6fe63..7fc8863 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -326,6 +326,15 @@
it.port = TEST_PORT
}
+ private fun makePacketReader(network: TestTapNetwork = testNetwork1) = PollPacketReader(
+ Handler(handlerThread.looper),
+ network.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ ).also {
+ it.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ }
+
@After
fun tearDown() {
runAsShell(MANAGE_TEST_NETWORKS) {
@@ -1298,14 +1307,7 @@
assumeTrue(TestUtils.shouldTestTApis())
val si = makeTestServiceInfo(testNetwork1.network)
-
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
+ val packetReader = makePacketReader()
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
@@ -1345,13 +1347,7 @@
parseNumericAddress("2001:db8::3"))
}
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
+ val packetReader = makePacketReader()
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
@@ -1391,13 +1387,7 @@
hostname = customHostname
}
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
+ val packetReader = makePacketReader()
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
@@ -1438,13 +1428,7 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
+ val packetReader = makePacketReader()
tryTest {
assertNotNull(packetReader.pollForAdvertisement(serviceName, serviceType),
@@ -1518,13 +1502,7 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
+ val packetReader = makePacketReader()
tryTest {
assertNotNull(packetReader.pollForAdvertisement(serviceName, serviceType),
@@ -1587,13 +1565,7 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
+ val packetReader = makePacketReader()
tryTest {
assertNotNull(packetReader.pollForAdvertisement(serviceName, serviceType),
@@ -1630,13 +1602,7 @@
fun testDiscoveryWithPtrOnlyResponse_ServiceIsFound() {
// Register service on testNetwork1
val discoveryRecord = NsdDiscoveryRecord()
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
+ val packetReader = makePacketReader()
nsdManager.discoverServices(
serviceType,
@@ -1675,9 +1641,12 @@
assertEmpty(it.hostAddresses)
assertEquals(0, it.attributes.size)
}
- } cleanup {
+ } cleanupStep {
nsdManager.stopServiceDiscovery(discoveryRecord)
discoveryRecord.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
}
@@ -1688,79 +1657,77 @@
fun testResolveWhenServerSendsNoAdditionalRecord() {
// Resolve service on testNetwork1
val resolveRecord = NsdResolveRecord()
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
-
+ val packetReader = makePacketReader()
val si = makeTestServiceInfo(testNetwork1.network)
nsdManager.resolveService(si, { it.run() }, resolveRecord)
- val serviceFullName = "$serviceName.$serviceType.local"
- // The query should ask for ANY, since both SRV and TXT are requested. Note legacy
- // mdnsresponder will ask for SRV and TXT separately, and will not proceed to asking for
- // address records without an answer for both.
- val srvTxtQuery = packetReader.pollForQuery(serviceFullName, DnsResolver.TYPE_ANY)
- assertNotNull(srvTxtQuery)
+ tryTest {
+ val serviceFullName = "$serviceName.$serviceType.local"
+ // The query should ask for ANY, since both SRV and TXT are requested. Note legacy
+ // mdnsresponder will ask for SRV and TXT separately, and will not proceed to asking for
+ // address records without an answer for both.
+ val srvTxtQuery = packetReader.pollForQuery(serviceFullName, DnsResolver.TYPE_ANY)
+ assertNotNull(srvTxtQuery)
- /*
- Generated with:
- scapy.raw(scapy.dns_compress(scapy.DNS(rd=0, qr=1, aa=1, qd = None, an =
- scapy.DNSRRSRV(rrname='NsdTest123456789._nmt123456789._tcp.local',
- rclass=0x8001, port=31234, target='testhost.local', ttl=120) /
- scapy.DNSRR(rrname='NsdTest123456789._nmt123456789._tcp.local', type='TXT', ttl=120,
- rdata='testkey=testvalue')
- ))).hex()
- */
- val srvTxtResponsePayload = HexDump.hexStringToByteArray(
- "000084000000000200000000104" +
- "e7364546573743132333435363738390d5f6e6d74313233343536373839045f746370056c6f6" +
- "3616c0000218001000000780011000000007a020874657374686f7374c030c00c00100001000" +
- "00078001211746573746b65793d7465737476616c7565"
- )
- replaceServiceNameAndTypeWithTestSuffix(srvTxtResponsePayload)
- packetReader.sendResponse(buildMdnsPacket(srvTxtResponsePayload))
+ /*
+ Generated with:
+ scapy.raw(scapy.dns_compress(scapy.DNS(rd=0, qr=1, aa=1, qd = None, an =
+ scapy.DNSRRSRV(rrname='NsdTest123456789._nmt123456789._tcp.local',
+ rclass=0x8001, port=31234, target='testhost.local', ttl=120) /
+ scapy.DNSRR(rrname='NsdTest123456789._nmt123456789._tcp.local', type='TXT', ttl=120,
+ rdata='testkey=testvalue')
+ ))).hex()
+ */
+ val srvTxtResponsePayload = HexDump.hexStringToByteArray(
+ "000084000000000200000000104" +
+ "e7364546573743132333435363738390d5f6e6d74313233343536373839045f7463" +
+ "70056c6f63616c0000218001000000780011000000007a020874657374686f7374c" +
+ "030c00c0010000100000078001211746573746b65793d7465737476616c7565"
+ )
+ replaceServiceNameAndTypeWithTestSuffix(srvTxtResponsePayload)
+ packetReader.sendResponse(buildMdnsPacket(srvTxtResponsePayload))
- val testHostname = "testhost.local"
- val addressQuery = packetReader.pollForQuery(
- testHostname,
- DnsResolver.TYPE_A,
- DnsResolver.TYPE_AAAA
- )
- assertNotNull(addressQuery)
+ val testHostname = "testhost.local"
+ val addressQuery = packetReader.pollForQuery(
+ testHostname,
+ DnsResolver.TYPE_A,
+ DnsResolver.TYPE_AAAA
+ )
+ assertNotNull(addressQuery)
- /*
- Generated with:
- scapy.raw(scapy.dns_compress(scapy.DNS(rd=0, qr=1, aa=1, qd = None, an =
- scapy.DNSRR(rrname='testhost.local', type='A', ttl=120,
- rdata='192.0.2.123') /
- scapy.DNSRR(rrname='testhost.local', type='AAAA', ttl=120,
- rdata='2001:db8::123')
- ))).hex()
- */
- val addressPayload = HexDump.hexStringToByteArray(
- "0000840000000002000000000874657374" +
- "686f7374056c6f63616c0000010001000000780004c000027bc00c001c000100000078001020" +
- "010db8000000000000000000000123"
- )
- packetReader.sendResponse(buildMdnsPacket(addressPayload))
+ /*
+ Generated with:
+ scapy.raw(scapy.dns_compress(scapy.DNS(rd=0, qr=1, aa=1, qd = None, an =
+ scapy.DNSRR(rrname='testhost.local', type='A', ttl=120,
+ rdata='192.0.2.123') /
+ scapy.DNSRR(rrname='testhost.local', type='AAAA', ttl=120,
+ rdata='2001:db8::123')
+ ))).hex()
+ */
+ val addressPayload = HexDump.hexStringToByteArray(
+ "0000840000000002000000000874657374" +
+ "686f7374056c6f63616c0000010001000000780004c000027bc00c001c000100000" +
+ "078001020010db8000000000000000000000123"
+ )
+ packetReader.sendResponse(buildMdnsPacket(addressPayload))
- val serviceResolved = resolveRecord.expectCallback<ServiceResolved>()
- serviceResolved.serviceInfo.let {
- assertEquals(serviceName, it.serviceName)
- assertEquals(".$serviceType", it.serviceType)
- assertEquals(testNetwork1.network, it.network)
- assertEquals(31234, it.port)
- assertEquals(1, it.attributes.size)
- assertArrayEquals("testvalue".encodeToByteArray(), it.attributes["testkey"])
+ val serviceResolved = resolveRecord.expectCallback<ServiceResolved>()
+ serviceResolved.serviceInfo.let {
+ assertEquals(serviceName, it.serviceName)
+ assertEquals(".$serviceType", it.serviceType)
+ assertEquals(testNetwork1.network, it.network)
+ assertEquals(31234, it.port)
+ assertEquals(1, it.attributes.size)
+ assertArrayEquals("testvalue".encodeToByteArray(), it.attributes["testkey"])
+ }
+ assertEquals(
+ setOf(parseNumericAddress("192.0.2.123"), parseNumericAddress("2001:db8::123")),
+ serviceResolved.serviceInfo.hostAddresses.toSet()
+ )
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
- assertEquals(
- setOf(parseNumericAddress("192.0.2.123"), parseNumericAddress("2001:db8::123")),
- serviceResolved.serviceInfo.hostAddresses.toSet()
- )
}
@Test
@@ -1774,13 +1741,9 @@
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
var nsResponder: NSResponder? = null
+ val packetReader = makePacketReader()
tryTest {
registerService(registrationRecord, si)
- val packetReader = PollPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
- packetReader.startAsyncForTest()
-
- handlerThread.waitForIdle(TIMEOUT_MS)
/*
Send a "query unicast" query.
Generated with:
@@ -1805,10 +1768,13 @@
pkt.dstAddr == testSrcAddr
}
assertNotNull(reply)
- } cleanup {
+ } cleanupStep {
nsResponder?.stop()
nsdManager.unregisterService(registrationRecord)
registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
}
@@ -1824,13 +1790,9 @@
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
var nsResponder: NSResponder? = null
+ val packetReader = makePacketReader()
tryTest {
registerService(registrationRecord, si)
- val packetReader = PollPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
- packetReader.startAsyncForTest()
-
- handlerThread.waitForIdle(TIMEOUT_MS)
/*
Send a query with a known answer. Expect to receive a response containing TXT record
only.
@@ -1895,10 +1857,13 @@
pkt.dstAddr == testSrcAddr
}
assertNotNull(reply2)
- } cleanup {
+ } cleanupStep {
nsResponder?.stop()
nsdManager.unregisterService(registrationRecord)
registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
}
@@ -1914,13 +1879,9 @@
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
var nsResponder: NSResponder? = null
+ val packetReader = makePacketReader()
tryTest {
registerService(registrationRecord, si)
- val packetReader = PollPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
- packetReader.startAsyncForTest()
-
- handlerThread.waitForIdle(TIMEOUT_MS)
/*
Send a query with truncated bit set.
Generated with:
@@ -1976,10 +1937,13 @@
pkt.dstAddr == testSrcAddr
}
assertNotNull(reply)
- } cleanup {
+ } cleanupStep {
nsResponder?.stop()
nsdManager.unregisterService(registrationRecord)
registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
}
@@ -1991,13 +1955,7 @@
// Register service on testNetwork1
val discoveryRecord = NsdDiscoveryRecord()
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
+ val packetReader = makePacketReader()
nsdManager.discoverServices(
serviceType,
@@ -2043,9 +2001,12 @@
pkt.isReplyFor("$serviceType.local", DnsResolver.TYPE_PTR)
}
assertNotNull(query)
- } cleanup {
+ } cleanupStep {
nsdManager.stopServiceDiscovery(discoveryRecord)
discoveryRecord.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
}
@@ -2355,14 +2316,7 @@
it.port = TEST_PORT
it.publicKey = publicKey
}
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
-
+ val packetReader = makePacketReader()
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
tryTest {
@@ -2394,8 +2348,11 @@
nsdManager.stopServiceDiscovery(discoveryRecord)
discoveryRecord.expectCallback<DiscoveryStopped>()
- } cleanup {
+ } cleanupStep {
nsdManager.unregisterService(registrationRecord)
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
}
@@ -2410,14 +2367,7 @@
parseNumericAddress("2001:db8::2"))
it.publicKey = publicKey
}
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
-
+ val packetReader = makePacketReader()
val registrationRecord = NsdRegistrationRecord()
tryTest {
registerService(registrationRecord, si)
@@ -2439,8 +2389,11 @@
it.nsType == DnsResolver.TYPE_A
}
assertEquals(3, addressRecords.size)
- } cleanup {
+ } cleanupStep {
nsdManager.unregisterService(registrationRecord)
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
}
@@ -2467,14 +2420,7 @@
it.hostAddresses = listOf()
it.publicKey = publicKey
}
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
-
+ val packetReader = makePacketReader()
val registrationRecord1 = NsdRegistrationRecord()
val registrationRecord2 = NsdRegistrationRecord()
tryTest {
@@ -2508,9 +2454,12 @@
assertTrue(keyRecords.any { it.dName == "$customHostname.local" })
assertTrue(keyRecords.all { it.ttl == NAME_RECORDS_TTL_MILLIS })
assertTrue(keyRecords.all { it.rr.contentEquals(publicKey) })
- } cleanup {
+ } cleanupStep {
nsdManager.unregisterService(registrationRecord1)
nsdManager.unregisterService(registrationRecord2)
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
}
@@ -2582,13 +2531,7 @@
"test_nsd_avoid_advertising_empty_txt_records",
"1"
)
- val packetReader = PollPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
- )
- packetReader.startAsyncForTest()
- handlerThread.waitForIdle(TIMEOUT_MS)
+ val packetReader = makePacketReader()
// Test behavior described in RFC6763 6.1: empty TXT records are not allowed, but TXT
// records with a zero length string are equivalent.
@@ -2607,12 +2550,85 @@
assertEquals(1, txtRecords.size)
// The TXT record should contain as single zero
assertContentEquals(byteArrayOf(0), txtRecords[0].rr)
- } cleanup {
+ } cleanupStep {
nsdManager.unregisterService(registrationRecord)
registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
}
}
+ private fun verifyCachedServicesRemoval(isCachedServiceRemoved: Boolean) {
+ val si = makeTestServiceInfo(testNetwork1.network)
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ registerService(registrationRecord, si)
+ // Register a discovery request.
+ val discoveryRecord = NsdDiscoveryRecord()
+ val packetReader = makePacketReader()
+
+ tryTest {
+ nsdManager.discoverServices(
+ serviceType,
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network,
+ { it.run() },
+ discoveryRecord
+ )
+
+ discoveryRecord.expectCallback<DiscoveryStarted>()
+ val foundInfo = discoveryRecord.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ assertEquals(testNetwork1.network, foundInfo.network)
+ // Verify that the service is not in the cache (a query is sent).
+ assertNotNull(packetReader.pollForQuery(
+ "$serviceType.local", DnsResolver.TYPE_PTR, timeoutMs = 0L))
+
+ // Stop discovery to trigger the cached services removal process.
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+
+ val serviceFullName = "$serviceName.$serviceType.local"
+ if (isCachedServiceRemoved) {
+ Thread.sleep(100L)
+ resolveService(foundInfo)
+ // Verify the resolution query will send because cached services are remove after
+ // exceeding the retention time.
+ assertNotNull(packetReader.pollForQuery(
+ serviceFullName, DnsResolver.TYPE_ANY, timeoutMs = 0L))
+ } else {
+ resolveService(foundInfo)
+ // Verify the resolution query will not be sent because services are still cached.
+ assertNull(packetReader.pollForQuery(
+ serviceFullName, DnsResolver.TYPE_ANY, timeoutMs = 0L))
+ }
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ }
+ }
+
+ @Test
+ fun testRemoveCachedServices() {
+ deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_cached_services_removal", "1")
+ verifyCachedServicesRemoval(isCachedServiceRemoved = false)
+ }
+
+ @Test
+ fun testRemoveCachedServices_ShortRetentionTime() {
+ deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_cached_services_removal", "1")
+ deviceConfigRule.setConfig(
+ NAMESPACE_TETHERING,
+ "test_nsd_cached_services_retention_time",
+ "1"
+ )
+ verifyCachedServicesRemoval(isCachedServiceRemoved = true)
+ }
+
private fun hasServiceTypeClientsForNetwork(clients: List<String>, network: Network): Boolean {
return clients.any { client -> client.substring(
client.indexOf("network=") + "network=".length,
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 83818be..d9bc7f7 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -19,7 +19,10 @@
java_defaults {
name: "CtsTetheringTestDefaults",
- defaults: ["cts_defaults"],
+ defaults: [
+ "cts_defaults",
+ "framework-connectivity-test-defaults",
+ ],
libs: [
"android.test.base.stubs.system",
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 1454d9a..a07c9ea 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -32,6 +32,7 @@
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.cts.util.CtsTetheringUtils.isAnyIfaceMatch;
+import static android.os.Process.INVALID_UID;
import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -244,24 +245,35 @@
assertFalse(tr.isExemptFromEntitlementCheck());
assertTrue(tr.getShouldShowEntitlementUi());
assertEquals(softApConfiguration, tr.getSoftApConfiguration());
+ assertEquals(INVALID_UID, tr.getUid());
+ assertNull(tr.getPackageName());
final LinkAddress localAddr = new LinkAddress("192.168.24.5/24");
final LinkAddress clientAddr = new LinkAddress("192.168.24.100/24");
final TetheringRequest tr2 = new TetheringRequest.Builder(TETHERING_USB)
.setStaticIpv4Addresses(localAddr, clientAddr)
.setExemptFromEntitlementCheck(true)
- .setShouldShowEntitlementUi(false).build();
+ .setShouldShowEntitlementUi(false)
+ .build();
+ int uid = 1000;
+ String packageName = "package";
+ tr2.setUid(uid);
+ tr2.setPackageName(packageName);
assertEquals(localAddr, tr2.getLocalIpv4Address());
assertEquals(clientAddr, tr2.getClientStaticIpv4Address());
assertEquals(TETHERING_USB, tr2.getTetheringType());
assertTrue(tr2.isExemptFromEntitlementCheck());
assertFalse(tr2.getShouldShowEntitlementUi());
+ assertEquals(uid, tr2.getUid());
+ assertEquals(packageName, tr2.getPackageName());
final TetheringRequest tr3 = new TetheringRequest.Builder(TETHERING_USB)
.setStaticIpv4Addresses(localAddr, clientAddr)
.setExemptFromEntitlementCheck(true)
.setShouldShowEntitlementUi(false).build();
+ tr3.setUid(uid);
+ tr3.setPackageName(packageName);
assertEquals(tr2, tr3);
}
diff --git a/tests/deflake/Android.bp b/tests/deflake/Android.bp
index 726e504..70a3655 100644
--- a/tests/deflake/Android.bp
+++ b/tests/deflake/Android.bp
@@ -40,7 +40,7 @@
"kotlin-test",
"net-host-tests-utils",
],
- data: [":FrameworksNetTests"],
+ device_common_data: [":FrameworksNetTests"],
test_suites: ["device-tests"],
// It will get build error if just set enabled to true. It fails with "windows_common"
// depends on some disabled modules that are used by this test and it looks like set
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 6892a42..9edf9bd 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -114,7 +114,7 @@
visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
}
-genrule {
+java_genrule {
name: "frameworks-net-tests-jarjar-rules",
defaults: ["jarjar-rules-combine-defaults"],
srcs: [
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
index 5ca7fcc..58420c0 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
@@ -163,19 +163,36 @@
doTestSatelliteNeverBecomeDefaultNetwork(restricted = false)
}
- private fun doTestUnregisterAfterReplacementSatisfier(destroyed: Boolean) {
+ private fun doTestUnregisterAfterReplacementSatisfier(destroyBeforeRequest: Boolean = false,
+ destroyAfterRequest: Boolean = false) {
val satelliteAgent = createSatelliteAgent("satellite0")
satelliteAgent.connect()
+ if (destroyBeforeRequest) {
+ satelliteAgent.unregisterAfterReplacement(timeoutMs = 5000)
+ }
+
val uids = setOf(TEST_PACKAGE_UID)
updateSatelliteNetworkFallbackUids(uids)
- if (destroyed) {
+ if (destroyBeforeRequest) {
+ verify(netd, never()).networkAddUidRangesParcel(any())
+ } else {
+ verify(netd).networkAddUidRangesParcel(
+ NativeUidRangeConfig(
+ satelliteAgent.network.netId,
+ toUidRangeStableParcels(uidRangesForUids(uids)),
+ PREFERENCE_ORDER_SATELLITE_FALLBACK
+ )
+ )
+ }
+
+ if (destroyAfterRequest) {
satelliteAgent.unregisterAfterReplacement(timeoutMs = 5000)
}
updateSatelliteNetworkFallbackUids(setOf())
- if (destroyed) {
+ if (destroyBeforeRequest || destroyAfterRequest) {
// If the network is already destroyed, networkRemoveUidRangesParcel should not be
// called.
verify(netd, never()).networkRemoveUidRangesParcel(any())
@@ -191,13 +208,18 @@
}
@Test
- fun testUnregisterAfterReplacementSatisfier_destroyed() {
- doTestUnregisterAfterReplacementSatisfier(destroyed = true)
+ fun testUnregisterAfterReplacementSatisfier_destroyBeforeRequest() {
+ doTestUnregisterAfterReplacementSatisfier(destroyBeforeRequest = true)
+ }
+
+ @Test
+ fun testUnregisterAfterReplacementSatisfier_destroyAfterRequest() {
+ doTestUnregisterAfterReplacementSatisfier(destroyAfterRequest = true)
}
@Test
fun testUnregisterAfterReplacementSatisfier_notDestroyed() {
- doTestUnregisterAfterReplacementSatisfier(destroyed = false)
+ doTestUnregisterAfterReplacementSatisfier()
}
private fun assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(uids: Set<Int>) {
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index 57a157d..50971e7 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -42,6 +42,7 @@
],
static_libs: [
"libnet_utils_device_common_bpfjni",
+ "libnet_utils_device_common_timerfdjni",
"libtcutils",
],
shared_libs: [
diff --git a/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java b/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java
index 2659d24..ea30e26 100644
--- a/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java
+++ b/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java
@@ -28,6 +28,7 @@
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkException;
import android.net.thread.ThreadNetworkManager;
@@ -45,6 +46,8 @@
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
+import com.google.android.material.switchmaterial.SwitchMaterial;
+
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -63,6 +66,7 @@
private TextView mTextNetworkInfo;
private TextView mMigrateNetworkState;
private TextView mEphemeralKeyStateText;
+ private SwitchMaterial mNat64Switch;
private Executor mMainExecutor;
private int mDeviceRole;
@@ -72,6 +76,7 @@
private String mEphemeralKey;
private Instant mEphemeralKeyExpiry;
private Timer mEphemeralKeyLifetimeTimer;
+ private ThreadConfiguration mThreadConfiguration;
private static final byte[] DEFAULT_ACTIVE_DATASET_TLVS =
base16().lowerCase()
@@ -110,6 +115,10 @@
}
}
+ private static String booleanToEnabledOrDisabled(boolean enabled) {
+ return enabled ? "Enabled" : "Disabled";
+ }
+
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -181,11 +190,16 @@
this.mActiveDataset = newActiveDataset;
updateState();
});
+ mThreadController.registerConfigurationCallback(
+ mMainExecutor, this::updateConfiguration);
}
mTextState = (TextView) view.findViewById(R.id.text_state);
mTextNetworkInfo = (TextView) view.findViewById(R.id.text_network_info);
mEphemeralKeyStateText = (TextView) view.findViewById(R.id.text_ephemeral_key_state);
+ mNat64Switch = (SwitchMaterial) view.findViewById(R.id.switch_nat64);
+ mNat64Switch.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> doSetNat64Enabled(isChecked));
if (mThreadController == null) {
mTextState.setText("Thread not supported!");
@@ -303,6 +317,34 @@
});
}
+ private void doSetNat64Enabled(boolean enabled) {
+ if (mThreadConfiguration == null) {
+ Log.e(TAG, "Thread configuration is not available");
+ return;
+ }
+ final ThreadConfiguration config =
+ new ThreadConfiguration.Builder(mThreadConfiguration)
+ .setNat64Enabled(enabled)
+ .build();
+ mThreadController.setConfiguration(
+ config,
+ mMainExecutor,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onError(ThreadNetworkException error) {
+ Log.e(
+ TAG,
+ "Failed to set NAT64 " + booleanToEnabledOrDisabled(enabled),
+ error);
+ }
+
+ @Override
+ public void onResult(Void v) {
+ Log.i(TAG, "Successfully set NAT64 " + booleanToEnabledOrDisabled(enabled));
+ }
+ });
+ }
+
private void updateState() {
Log.i(
TAG,
@@ -368,4 +410,11 @@
}
mTextNetworkInfo.setText(sb.toString());
}
+
+ private void updateConfiguration(ThreadConfiguration config) {
+ Log.i(TAG, "Updating configuration: " + config);
+
+ mThreadConfiguration = config;
+ mNat64Switch.setChecked(config.isNat64Enabled());
+ }
}
diff --git a/thread/demoapp/res/layout/main_activity.xml b/thread/demoapp/res/layout/main_activity.xml
index 12072e5..d874db1 100644
--- a/thread/demoapp/res/layout/main_activity.xml
+++ b/thread/demoapp/res/layout/main_activity.xml
@@ -21,6 +21,7 @@
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
tools:context=".MainActivity">
<LinearLayout
diff --git a/thread/demoapp/res/layout/thread_network_settings_fragment.xml b/thread/demoapp/res/layout/thread_network_settings_fragment.xml
index 84d984b..47ce62a 100644
--- a/thread/demoapp/res/layout/thread_network_settings_fragment.xml
+++ b/thread/demoapp/res/layout/thread_network_settings_fragment.xml
@@ -19,11 +19,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:padding="8dp"
android:orientation="vertical"
tools:context=".ThreadNetworkSettingsFragment" >
@@ -40,28 +39,28 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textSize="16dp"
+ android:textSize="16sp"
android:textStyle="bold"
android:text="State" />
<TextView
android:id="@+id/text_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textSize="12dp"
+ android:textSize="12sp"
android:typeface="monospace" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
- android:textSize="16dp"
+ android:textSize="16sp"
android:textStyle="bold"
android:text="Network Info" />
<TextView
android:id="@+id/text_network_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textSize="12dp" />
+ android:textSize="12sp" />
<Button android:id="@+id/button_migrate_network"
android:layout_width="wrap_content"
@@ -71,7 +70,7 @@
android:id="@+id/text_migrate_network_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textSize="12dp" />
+ android:textSize="12sp" />
<Button android:id="@+id/button_activate_ephemeral_key_mode"
android:layout_width="wrap_content"
@@ -86,14 +85,28 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
- android:textSize="16dp"
+ android:textSize="16sp"
android:textStyle="bold"
android:text="Ephemeral Key State" />
<TextView
android:id="@+id/text_ephemeral_key_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="50dp"
- android:textSize="12dp" />
+ android:textSize="12sp" />
+
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="Configuration"
+ android:textSize="16sp"
+ android:textStyle="bold" />
+ <com.google.android.material.switchmaterial.SwitchMaterial
+ android:id="@+id/switch_nat64"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:checked="false"
+ android:text="NAT64" />
+
</LinearLayout>
</ScrollView>
diff --git a/thread/framework/java/android/net/thread/ThreadConfiguration.java b/thread/framework/java/android/net/thread/ThreadConfiguration.java
index e6fa1ef..edb5021 100644
--- a/thread/framework/java/android/net/thread/ThreadConfiguration.java
+++ b/thread/framework/java/android/net/thread/ThreadConfiguration.java
@@ -61,7 +61,11 @@
return mNat64Enabled;
}
- /** Returns {@code true} if DHCPv6 Prefix Delegation is enabled. */
+ /**
+ * Returns {@code true} if DHCPv6 Prefix Delegation is enabled.
+ *
+ * @hide
+ */
public boolean isDhcpv6PdEnabled() {
return mDhcpv6PdEnabled;
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 4e812fb..ba4eeaf 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -78,6 +78,8 @@
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalNetworkConfig;
import android.net.LocalNetworkInfo;
@@ -120,6 +122,8 @@
import com.android.connectivity.resources.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.RoutingCoordinatorManager;
+import com.android.net.module.util.IIpv4PrefixRequest;
import com.android.net.module.util.SharedLog;
import com.android.server.ServiceManagerWrapper;
import com.android.server.connectivity.ConnectivityResources;
@@ -141,11 +145,13 @@
import java.io.IOException;
import java.net.Inet6Address;
+import java.net.InetAddress;
import java.security.SecureRandom;
import java.time.Clock;
import java.time.DateTimeException;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -193,10 +199,12 @@
private final NetworkProvider mNetworkProvider;
private final Supplier<IOtDaemon> mOtDaemonSupplier;
private final ConnectivityManager mConnectivityManager;
+ private final RoutingCoordinatorManager mRoutingCoordinatorManager;
private final TunInterfaceController mTunIfController;
private final InfraInterfaceController mInfraIfController;
private final NsdPublisher mNsdPublisher;
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
+ private final Nat64CidrController mNat64CidrController = new Nat64CidrController();
private final ConnectivityResources mResources;
private final Supplier<String> mCountryCodeSupplier;
private final Map<IConfigurationReceiver, IBinder.DeathRecipient> mConfigurationReceivers =
@@ -229,6 +237,7 @@
NetworkProvider networkProvider,
Supplier<IOtDaemon> otDaemonSupplier,
ConnectivityManager connectivityManager,
+ RoutingCoordinatorManager routingCoordinatorManager,
TunInterfaceController tunIfController,
InfraInterfaceController infraIfController,
ThreadPersistentSettings persistentSettings,
@@ -242,6 +251,7 @@
mNetworkProvider = networkProvider;
mOtDaemonSupplier = otDaemonSupplier;
mConnectivityManager = connectivityManager;
+ mRoutingCoordinatorManager = routingCoordinatorManager;
mTunIfController = tunIfController;
mInfraIfController = infraIfController;
mUpstreamNetworkRequest = newUpstreamNetworkRequest();
@@ -266,13 +276,19 @@
NetworkProvider networkProvider =
new NetworkProvider(context, handlerThread.getLooper(), "ThreadNetworkProvider");
Map<Network, LinkProperties> networkToLinkProperties = new HashMap<>();
+ final ConnectivityManager connectivityManager =
+ context.getSystemService(ConnectivityManager.class);
+ final RoutingCoordinatorManager routingCoordinatorManager =
+ new RoutingCoordinatorManager(
+ context, connectivityManager.getRoutingCoordinatorService());
return new ThreadNetworkControllerService(
context,
handler,
networkProvider,
() -> IOtDaemon.Stub.asInterface(ServiceManagerWrapper.waitForService("ot_daemon")),
- context.getSystemService(ConnectivityManager.class),
+ connectivityManager,
+ routingCoordinatorManager,
new TunInterfaceController(TUN_IF_NAME),
new InfraInterfaceController(),
persistentSettings,
@@ -351,6 +367,7 @@
mCountryCodeSupplier.get());
otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
mOtDaemon = otDaemon;
+ mHandler.post(mNat64CidrController::maybeUpdateNat64Cidr);
return mOtDaemon;
}
@@ -444,24 +461,26 @@
}
public void initialize() {
- mHandler.post(
- () -> {
- LOG.v(
- "Initializing Thread system service: Thread is "
- + (shouldEnableThread() ? "enabled" : "disabled"));
- try {
- mTunIfController.createTunInterface();
- } catch (IOException e) {
- throw new IllegalStateException(
- "Failed to create Thread tunnel interface", e);
- }
- mConnectivityManager.registerNetworkProvider(mNetworkProvider);
- requestUpstreamNetwork();
- registerThreadNetworkCallback();
- mUserRestricted = isThreadUserRestricted();
- registerUserRestrictionsReceiver();
- maybeInitializeOtDaemon();
- });
+ mHandler.post(() -> initializeInternal());
+ }
+
+ private void initializeInternal() {
+ checkOnHandlerThread();
+
+ LOG.v(
+ "Initializing Thread system service: Thread is "
+ + (shouldEnableThread() ? "enabled" : "disabled"));
+ try {
+ mTunIfController.createTunInterface();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to create Thread tunnel interface", e);
+ }
+ mConnectivityManager.registerNetworkProvider(mNetworkProvider);
+ requestUpstreamNetwork();
+ registerThreadNetworkCallback();
+ mUserRestricted = isThreadUserRestricted();
+ registerUserRestrictionsReceiver();
+ maybeInitializeOtDaemon();
}
/**
@@ -587,6 +606,7 @@
} catch (RemoteException | ThreadNetworkException e) {
LOG.e("otDaemon.setConfiguration failed. Config: " + configuration, e);
}
+ mNat64CidrController.maybeUpdateNat64Cidr();
}
private static OtDaemonConfiguration newOtDaemonConfig(
@@ -831,7 +851,7 @@
mHandler.getLooper(),
LOG.getTag(),
netCaps,
- mTunIfController.getLinkProperties(),
+ getTunIfLinkProperties(),
newLocalNetworkConfig(),
score,
new NetworkAgentConfig.Builder().build(),
@@ -1324,6 +1344,7 @@
}
setInfraLinkInterfaceName(newInfraLinkState.interfaceName);
setInfraLinkNat64Prefix(newInfraLinkState.nat64Prefix);
+ setInfraLinkDnsServers(newInfraLinkState.dnsServers);
mInfraLinkState = newInfraLinkState;
}
@@ -1357,6 +1378,16 @@
}
}
+ private void setInfraLinkDnsServers(List<String> newDnsServers) {
+ try {
+ getOtDaemon()
+ .setInfraLinkDnsServers(
+ newDnsServers, new LoggingOtStatusReceiver("setInfraLinkDnsServers"));
+ } catch (RemoteException | ThreadNetworkException e) {
+ LOG.e("Failed to set infra link DNS servers " + newDnsServers, e);
+ }
+ }
+
private void handleThreadInterfaceStateChanged(boolean isUp) {
try {
mTunIfController.setInterfaceUp(isUp);
@@ -1389,9 +1420,7 @@
// The OT daemon can send link property updates before the networkAgent is
// registered
- if (mNetworkAgent != null) {
- mNetworkAgent.sendLinkProperties(mTunIfController.getLinkProperties());
- }
+ maybeSendLinkProperties();
}
private void handlePrefixChanged(List<OnMeshPrefixConfig> onMeshPrefixConfigList) {
@@ -1401,9 +1430,18 @@
// The OT daemon can send link property updates before the networkAgent is
// registered
- if (mNetworkAgent != null) {
- mNetworkAgent.sendLinkProperties(mTunIfController.getLinkProperties());
+ maybeSendLinkProperties();
+ }
+
+ private void maybeSendLinkProperties() {
+ if (mNetworkAgent == null) {
+ return;
}
+ mNetworkAgent.sendLinkProperties(getTunIfLinkProperties());
+ }
+
+ private LinkProperties getTunIfLinkProperties() {
+ return mTunIfController.getLinkPropertiesWithNat64Cidr(mNat64CidrController.mNat64Cidr);
}
@RequiresPermission(
@@ -1495,7 +1533,17 @@
}
return new InfraLinkState.Builder()
.setInterfaceName(linkProperties.getInterfaceName())
- .setNat64Prefix(nat64Prefix);
+ .setNat64Prefix(nat64Prefix)
+ .setDnsServers(addressesToStrings(linkProperties.getDnsServers()));
+ }
+
+ private static List<String> addressesToStrings(List<InetAddress> addresses) {
+ List<String> strings = new ArrayList<>();
+
+ for (InetAddress address : addresses) {
+ strings.add(address.getHostAddress());
+ }
+ return strings;
}
private static final class CallbackMetadata {
@@ -1849,4 +1897,64 @@
mHandler.post(() -> handlePrefixChanged(onMeshPrefixConfigList));
}
}
+
+ private final class Nat64CidrController extends IIpv4PrefixRequest.Stub {
+ private static final int RETRY_DELAY_ON_FAILURE_MILLIS = 600_000; // 10 minutes
+
+ @Nullable private LinkAddress mNat64Cidr;
+
+ @Override
+ public void onIpv4PrefixConflict(IpPrefix prefix) {
+ mHandler.post(() -> onIpv4PrefixConflictInternal(prefix));
+ }
+
+ private void onIpv4PrefixConflictInternal(IpPrefix prefix) {
+ checkOnHandlerThread();
+
+ LOG.i("Conflict on NAT64 CIDR: " + prefix);
+ maybeReleaseNat64Cidr();
+ maybeUpdateNat64Cidr();
+ }
+
+ public void maybeUpdateNat64Cidr() {
+ checkOnHandlerThread();
+
+ if (mPersistentSettings.getConfiguration().isNat64Enabled()) {
+ maybeRequestNat64Cidr();
+ } else {
+ maybeReleaseNat64Cidr();
+ }
+ try {
+ getOtDaemon()
+ .setNat64Cidr(
+ mNat64Cidr == null ? null : mNat64Cidr.toString(),
+ new LoggingOtStatusReceiver("setNat64Cidr"));
+ } catch (RemoteException | ThreadNetworkException e) {
+ LOG.e("Failed to set NAT64 CIDR at otd-daemon", e);
+ }
+ maybeSendLinkProperties();
+ }
+
+ private void maybeRequestNat64Cidr() {
+ if (mNat64Cidr != null) {
+ return;
+ }
+ final LinkAddress downstreamAddress =
+ mRoutingCoordinatorManager.requestDownstreamAddress(this);
+ if (downstreamAddress == null) {
+ mHandler.postDelayed(() -> maybeUpdateNat64Cidr(), RETRY_DELAY_ON_FAILURE_MILLIS);
+ }
+ mNat64Cidr = downstreamAddress;
+ LOG.i("Allocated NAT64 CIDR: " + mNat64Cidr);
+ }
+
+ private void maybeReleaseNat64Cidr() {
+ if (mNat64Cidr == null) {
+ return;
+ }
+ LOG.i("Released NAT64 CIDR: " + mNat64Cidr);
+ mNat64Cidr = null;
+ mRoutingCoordinatorManager.releaseDownstream(this);
+ }
+ }
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
index 1eddebf..5d869df 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
@@ -19,10 +19,12 @@
import android.annotation.Nullable;
import android.content.Context;
import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.IConfigurationReceiver;
import android.net.thread.IOperationReceiver;
import android.net.thread.IOutputReceiver;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkException;
import android.os.Binder;
import android.os.Process;
@@ -56,6 +58,7 @@
private static final Duration MIGRATE_TIMEOUT = Duration.ofSeconds(2);
private static final Duration FORCE_STOP_TIMEOUT = Duration.ofSeconds(1);
private static final Duration OT_CTL_COMMAND_TIMEOUT = Duration.ofSeconds(5);
+ private static final Duration CONFIG_TIMEOUT = Duration.ofSeconds(1);
private static final String PERMISSION_THREAD_NETWORK_TESTING =
"android.permission.THREAD_NETWORK_TESTING";
@@ -118,6 +121,8 @@
pw.println(" Sets country code to <two-letter code> or left for normal value");
pw.println(" ot-ctl <subcommand>");
pw.println(" Runs ot-ctl command");
+ pw.println(" config [name] [value]");
+ pw.println(" Gets the config or sets the value for a config entry");
}
@Override
@@ -144,6 +149,8 @@
return forceCountryCode();
case "get-country-code":
return getCountryCode();
+ case "config":
+ return handleConfigCommand();
case "ot-ctl":
return handleOtCtlCommand();
default:
@@ -261,6 +268,68 @@
return 0;
}
+ private int handleConfigCommand() {
+ ensureTestingPermission();
+
+ // Get config
+ if (peekNextArg() == null) {
+ try {
+ final ThreadConfiguration config = getConfig();
+ getOutputWriter().println("Thread configuration = " + config);
+ } catch (AssertionError e) {
+ getErrorWriter().println("Failed: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ // Set config
+ final String name = getNextArg();
+ final String value = getNextArg();
+ try {
+ setConfig(name, value);
+ } catch (AssertionError | IllegalArgumentException e) {
+ getErrorWriter().println(e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private ThreadConfiguration getConfig() throws AssertionError {
+ final CompletableFuture<ThreadConfiguration> future = new CompletableFuture<>();
+ mControllerService.registerConfigurationCallback(
+ new IConfigurationReceiver.Stub() {
+ @Override
+ public void onConfigurationChanged(ThreadConfiguration config) {
+ future.complete(config);
+ }
+ });
+ try {
+ return future.get(CONFIG_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ throw new AssertionError("Failed to get config within timeout", e);
+ }
+ }
+
+ private void setConfig(String name, String value)
+ throws IllegalArgumentException, AssertionError {
+ if (name == null || value == null) {
+ throw new IllegalArgumentException(
+ "Invalid config name = " + name + ", value=" + value);
+ }
+ final ThreadConfiguration oldConfig = getConfig();
+ final ThreadConfiguration.Builder newConfigBuilder =
+ new ThreadConfiguration.Builder(oldConfig);
+ switch (name) {
+ case "nat64" -> newConfigBuilder.setNat64Enabled(argEnabledOrDisabled(value));
+ case "pd" -> newConfigBuilder.setDhcpv6PdEnabled(argEnabledOrDisabled(value));
+ default -> throw new IllegalArgumentException("Invalid config name: " + name);
+ }
+ CompletableFuture<Void> future = new CompletableFuture();
+ mControllerService.setConfiguration(newConfigBuilder.build(), newOperationReceiver(future));
+ waitForFuture(future, CONFIG_TIMEOUT, mErrorWriter);
+ }
+
private static final class OutputReceiver extends IOutputReceiver.Stub {
private final CompletableFuture<Void> future;
private final PrintWriter outputWriter;
@@ -359,6 +428,10 @@
}
}
+ private static boolean argEnabledOrDisabled(String arg) {
+ return argTrueOrFalse(arg, "enabled", "disabled");
+ }
+
private boolean getNextArgRequiredTrueOrFalse(String trueString, String falseString) {
String nextArg = getNextArgRequired();
return argTrueOrFalse(nextArg, trueString, falseString);
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index 85a0371..520a434 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -92,8 +92,19 @@
}
/** Returns link properties of the Thread TUN interface. */
- public LinkProperties getLinkProperties() {
- return mLinkProperties;
+ private LinkProperties getLinkProperties() {
+ return new LinkProperties(mLinkProperties);
+ }
+
+ /** Returns link properties of the Thread TUN interface with the given NAT64 CIDR. */
+ // TODO: manage the NAT64 CIDR in the TunInterfaceController
+ public LinkProperties getLinkPropertiesWithNat64Cidr(@Nullable LinkAddress nat64Cidr) {
+ final LinkProperties lp = getLinkProperties();
+ if (nat64Cidr != null) {
+ lp.addLinkAddress(nat64Cidr);
+ lp.addRoute(getRouteForAddress(nat64Cidr));
+ }
+ return lp;
}
/**
@@ -148,6 +159,9 @@
/** Adds a new address to the interface. */
public void addAddress(LinkAddress address) {
+ if (!(address.getAddress() instanceof Inet6Address)) {
+ return;
+ }
LOG.v("Adding address " + address + " with flags: " + address.getFlags());
long preferredLifetimeSeconds;
@@ -172,7 +186,7 @@
(address.getExpirationTime() - SystemClock.elapsedRealtime()) / 1000L,
0L);
}
-
+ // Only apply to Ipv6 address
if (!NetlinkUtils.sendRtmNewAddressRequest(
Os.if_nametoindex(mIfName),
address.getAddress(),
@@ -190,6 +204,9 @@
/** Removes an address from the interface. */
public void removeAddress(LinkAddress address) {
+ if (!(address.getAddress() instanceof Inet6Address)) {
+ return;
+ }
LOG.v("Removing address " + address);
// Intentionally update the mLinkProperties before send netlink message because the
@@ -197,6 +214,7 @@
// when the netlink request below fails
mLinkProperties.removeLinkAddress(address);
mLinkProperties.removeRoute(getRouteForAddress(address));
+ // Only apply to Ipv6 address
if (!NetlinkUtils.sendRtmDelAddressRequest(
Os.if_nametoindex(mIfName),
(Inet6Address) address.getAddress(),
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index d9ce9e1..92227b4 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -1265,7 +1265,10 @@
assertThat(txtMap.get("rv")).isNotNull();
assertThat(txtMap.get("tv")).isNotNull();
- assertThat(txtMap.get("sb")).isNotNull();
+ // Border Agent State Bitmap is 32 bits
+ assertThat(txtMap.get("sb").length).isEqualTo(4);
+ // The 12th bit (4th bit of the second byte) for ePSKc support should be set to 1.
+ assertThat(txtMap.get("sb")[2] & 8).isEqualTo(8);
}
@Test
@@ -1290,7 +1293,10 @@
Map<String, byte[]> txtMap = resolvedService.getAttributes();
assertThat(txtMap.get("rv")).isNotNull();
assertThat(txtMap.get("tv")).isNotNull();
- assertThat(txtMap.get("sb")).isNotNull();
+ // Border Agent State Bitmap is 32 bits
+ assertThat(txtMap.get("sb").length).isEqualTo(4);
+ // The 12th bit (4th bit of the second byte) for ePSKc support should be set to 1.
+ assertThat(txtMap.get("sb")[2] & 8).isEqualTo(8);
assertThat(txtMap.get("id").length).isEqualTo(16);
}
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index cf7a4f7..f6dd6b9 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
+import static android.net.thread.utils.IntegrationTestUtils.buildIcmpv4EchoReply;
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv4Packet;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
@@ -77,9 +78,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -101,7 +104,6 @@
(Inet6Address) parseNumericAddress("ff03::1234");
private static final Inet4Address IPV4_SERVER_ADDR =
(Inet4Address) parseNumericAddress("8.8.8.8");
- private static final String NAT64_CIDR = "192.168.255.0/24";
private static final IpPrefix DHCP6_PD_PREFIX = new IpPrefix("2001:db8::/64");
private static final IpPrefix AIL_NAT64_PREFIX = new IpPrefix("2001:db8:1234::/96");
private static final Inet6Address AIL_NAT64_SYNTHESIZED_SERVER_ADDR =
@@ -647,17 +649,27 @@
}
@Test
- public void nat64_threadDevicePingIpv4InfraDevice_outboundPacketIsForwarded() throws Exception {
+ public void nat64_threadDevicePingIpv4InfraDevice_outboundPacketIsForwardedAndReplyIsReceived()
+ throws Exception {
FullThreadDevice ftd = mFtds.get(0);
joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
- // TODO: enable NAT64 via ThreadNetworkController API instead of ot-ctl
- mOtCtl.setNat64Cidr(NAT64_CIDR);
- mOtCtl.setNat64Enabled(true);
+ mController.setNat64EnabledAndWait(true);
waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), UPDATE_NAT64_PREFIX_TIMEOUT);
+ Thread echoReplyThread = new Thread(() -> respondToEchoRequestOnce(IPV4_SERVER_ADDR));
+ echoReplyThread.start();
- ftd.ping(IPV4_SERVER_ADDR);
+ assertThat(ftd.ping(IPV4_SERVER_ADDR, 1 /* count */)).isEqualTo(1);
- assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMP_ECHO, null, IPV4_SERVER_ADDR));
+ echoReplyThread.join();
+ }
+
+ private void respondToEchoRequestOnce(Inet4Address dstAddress) {
+ byte[] echoRequest = pollForIcmpPacketOnInfraNetwork(ICMP_ECHO, null, dstAddress);
+ assertNotNull(echoRequest);
+ try {
+ mInfraNetworkReader.sendResponse(buildIcmpv4EchoReply(ByteBuffer.wrap(echoRequest)));
+ } catch (IOException ignored) {
+ }
}
@Test
@@ -685,8 +697,7 @@
mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
FullThreadDevice ftd = mFtds.get(0);
joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
- // TODO: enable NAT64 via ThreadNetworkController API instead of ot-ctl
- mOtCtl.setNat64Enabled(true);
+ mController.setNat64EnabledAndWait(true);
mOtCtl.addPrefixInNetworkData(DHCP6_PD_PREFIX, "paros", "med");
waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), UPDATE_NAT64_PREFIX_TIMEOUT);
diff --git a/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java b/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
index 87219d3..32e3b95 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
@@ -19,6 +19,7 @@
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
+import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_CONFIG;
import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
@@ -79,6 +80,7 @@
public void tearDown() throws Exception {
mFtd.destroy();
ensureThreadEnabled();
+ mController.setConfigurationAndWait(DEFAULT_CONFIG);
}
private static void ensureThreadEnabled() {
@@ -179,6 +181,27 @@
assertThat(result).endsWith("Done\r\n");
}
+ @Test
+ public void config_getConfig_expectedValueIsPrinted() throws Exception {
+ ThreadConfiguration config =
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build();
+ mController.setConfigurationAndWait(config);
+
+ final String result = runThreadCommand("config");
+
+ assertThat(result).contains("Nat64Enabled=true");
+ }
+
+ @Test
+ public void config_setConfig_expectedValueIsSet() throws Exception {
+ ThreadConfiguration config = new ThreadConfiguration.Builder().build();
+ mController.setConfigurationAndWait(config);
+
+ runThreadCommand("config nat64 enabled");
+
+ assertThat(mController.getConfiguration().isNat64Enabled()).isTrue();
+ }
+
private static String runThreadCommand(String cmd) {
return runShellCommandOrThrow("cmd thread_network " + cmd);
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
index 116fb72..dc2a9c9 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
@@ -32,14 +32,21 @@
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.net.thread.ActiveOperationalDataset
+import android.net.thread.ThreadConfiguration
import android.net.thread.ThreadNetworkController
import android.os.Build
import android.os.Handler
import android.os.SystemClock
import android.system.OsConstants
+import android.system.OsConstants.IPPROTO_ICMP
import androidx.test.core.app.ApplicationProvider
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.net.module.util.IpUtils
import com.android.net.module.util.NetworkStackConstants
+import com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET
+import com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET
+import com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN
+import com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET
import com.android.net.module.util.Struct
import com.android.net.module.util.structs.Icmpv4Header
import com.android.net.module.util.structs.Icmpv6Header
@@ -108,6 +115,9 @@
val DEFAULT_DATASET: ActiveOperationalDataset =
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS)
+ @JvmField
+ val DEFAULT_CONFIG = ThreadConfiguration.Builder().build()
+
/**
* Waits for the given [Supplier] to be true until given timeout.
*
@@ -303,6 +313,73 @@
return null
}
+ /** Builds an ICMPv4 Echo Reply packet to respond to the given ICMPv4 Echo Request packet. */
+ @JvmStatic
+ fun buildIcmpv4EchoReply(request: ByteBuffer): ByteBuffer? {
+ val requestIpv4Header = Struct.parse(Ipv4Header::class.java, request) ?: return null
+ val requestIcmpv4Header = Struct.parse(Icmpv4Header::class.java, request) ?: return null
+
+ val id = request.getShort()
+ val seq = request.getShort()
+
+ val payload = ByteBuffer.allocate(4 + request.limit() - request.position())
+ payload.putShort(id)
+ payload.putShort(seq)
+ payload.put(request)
+ payload.rewind()
+
+ val ipv4HeaderLen = Struct.getSize(Ipv4Header::class.java)
+ val Icmpv4HeaderLen = Struct.getSize(Icmpv4Header::class.java)
+ val payloadLen = payload.limit();
+
+ val reply = ByteBuffer.allocate(ipv4HeaderLen + Icmpv4HeaderLen + payloadLen)
+
+ // IPv4 header
+ val replyIpv4Header = Ipv4Header(
+ 0 /* TYPE OF SERVICE */,
+ 0.toShort().toInt()/* totalLength, calculate later */,
+ requestIpv4Header.id,
+ requestIpv4Header.flagsAndFragmentOffset,
+ 0x40 /* ttl */,
+ IPPROTO_ICMP.toByte(),
+ 0.toShort()/* checksum, calculate later */,
+ requestIpv4Header.dstIp /* srcIp */,
+ requestIpv4Header.srcIp /* dstIp */
+ )
+ replyIpv4Header.writeToByteBuffer(reply)
+
+ // ICMPv4 header
+ val replyIcmpv4Header = Icmpv4Header(
+ 0 /* type, ICMP_ECHOREPLY */,
+ requestIcmpv4Header.code,
+ 0.toShort() /* checksum, calculate later */
+ )
+ replyIcmpv4Header.writeToByteBuffer(reply)
+
+ // Payload
+ reply.put(payload)
+ reply.flip()
+
+ // Populate the IPv4 totalLength field.
+ reply.putShort(
+ IPV4_LENGTH_OFFSET, (ipv4HeaderLen + Icmpv4HeaderLen + payloadLen).toShort()
+ )
+
+ // Populate the IPv4 header checksum field.
+ reply.putShort(
+ IPV4_CHECKSUM_OFFSET, IpUtils.ipChecksum(reply, 0 /* headerOffset */)
+ )
+
+ // Populate the ICMP checksum field.
+ reply.putShort(
+ IPV4_HEADER_MIN_LEN + ICMP_CHECKSUM_OFFSET, IpUtils.icmpChecksum(
+ reply, IPV4_HEADER_MIN_LEN, Icmpv4HeaderLen + payloadLen
+ )
+ )
+
+ return reply
+ }
+
/** Returns the Prefix Information Options (PIO) extracted from an ICMPv6 RA message. */
@JvmStatic
fun getRaPios(raMsg: ByteArray?): List<PrefixInformationOption> {
diff --git a/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java b/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
index 4a30c45..4354702 100644
--- a/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
+++ b/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
@@ -200,7 +200,7 @@
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> mController.registerConfigurationCallback(directExecutor(), callback));
- future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
+ future.get(CONFIG_TIMEOUT.toSeconds(), SECONDS);
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> mController.unregisterConfigurationCallback(callback));
@@ -214,7 +214,14 @@
() ->
mController.setConfiguration(
config, directExecutor(), newOutcomeReceiver(future)));
- future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
+ future.get(CONFIG_TIMEOUT.toSeconds(), SECONDS);
+ }
+
+ public void setNat64EnabledAndWait(boolean enabled) throws Exception {
+ final ThreadConfiguration config = getConfiguration();
+ final ThreadConfiguration newConfig =
+ new ThreadConfiguration.Builder(config).setNat64Enabled(enabled).build();
+ setConfigurationAndWait(newConfig);
}
private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 7ac404f..e188491 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -44,6 +44,8 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNotNull;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -64,6 +66,7 @@
import android.content.Intent;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
@@ -91,9 +94,12 @@
import com.android.connectivity.resources.R;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.DnsTxtAttribute;
+import com.android.server.thread.openthread.IOtStatusReceiver;
import com.android.server.thread.openthread.MeshcopTxtAttributes;
+import com.android.server.thread.openthread.OtDaemonConfiguration;
import com.android.server.thread.openthread.testing.FakeOtDaemon;
import org.junit.Before;
@@ -164,8 +170,10 @@
private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
private static final String TEST_VENDOR_NAME = "test vendor";
private static final String TEST_MODEL_NAME = "test model";
+ private static final LinkAddress TEST_NAT64_CIDR = new LinkAddress("192.168.255.0/24");
@Mock private ConnectivityManager mMockConnectivityManager;
+ @Mock private RoutingCoordinatorManager mMockRoutingCoordinatorManager;
@Mock private NetworkAgent mMockNetworkAgent;
@Mock private TunInterfaceController mMockTunIfController;
@Mock private ParcelFileDescriptor mMockTunFd;
@@ -208,7 +216,10 @@
NetworkProvider networkProvider =
new NetworkProvider(mContext, mTestLooper.getLooper(), "ThreadNetworkProvider");
- mFakeOtDaemon = new FakeOtDaemon(handler);
+ when(mMockRoutingCoordinatorManager.requestDownstreamAddress(any()))
+ .thenReturn(TEST_NAT64_CIDR);
+
+ mFakeOtDaemon = spy(new FakeOtDaemon(handler));
when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
@@ -235,6 +246,7 @@
networkProvider,
() -> mFakeOtDaemon,
mMockConnectivityManager,
+ mMockRoutingCoordinatorManager,
mMockTunIfController,
mMockInfraIfController,
mPersistentSettings,
@@ -281,6 +293,37 @@
}
@Test
+ public void initialize_nat64Disabled_doesNotRequestNat64CidrAndConfiguresOtDaemon()
+ throws Exception {
+ ThreadConfiguration config =
+ new ThreadConfiguration.Builder().setNat64Enabled(false).build();
+ mPersistentSettings.putConfiguration(config);
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ verify(mMockRoutingCoordinatorManager, never()).requestDownstreamAddress(any());
+ verify(mFakeOtDaemon, times(1)).setNat64Cidr(isNull(), any());
+ verify(mFakeOtDaemon, never()).setNat64Cidr(isNotNull(), any());
+ }
+
+ @Test
+ public void initialize_nat64Enabled_requestsNat64CidrAndConfiguresAtOtDaemon()
+ throws Exception {
+ ThreadConfiguration config =
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build();
+ mPersistentSettings.putConfiguration(config);
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ verify(mMockRoutingCoordinatorManager, times(1)).requestDownstreamAddress(any());
+ verify(mFakeOtDaemon, times(1))
+ .setConfiguration(
+ new OtDaemonConfiguration.Builder().setNat64Enabled(true).build(),
+ null /* receiver */);
+ verify(mFakeOtDaemon, times(1)).setNat64Cidr(eq(TEST_NAT64_CIDR.toString()), any());
+ }
+
+ @Test
public void getMeshcopTxtAttributes_emptyVendorName_accepted() {
when(mResources.getString(eq(R.string.config_thread_vendor_name))).thenReturn("");
@@ -758,6 +801,71 @@
}
@Test
+ public void setConfiguration_enablesNat64_requestsNat64CidrAndConfiguresOtdaemon()
+ throws Exception {
+ mService.initialize();
+ mTestLooper.dispatchAll();
+ clearInvocations(mMockRoutingCoordinatorManager, mFakeOtDaemon);
+
+ final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
+ mService.setConfiguration(
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build(), mockReceiver);
+ mTestLooper.dispatchAll();
+
+ verify(mockReceiver, times(1)).onSuccess();
+ verify(mMockRoutingCoordinatorManager, times(1)).requestDownstreamAddress(any());
+ verify(mFakeOtDaemon, times(1))
+ .setConfiguration(
+ eq(new OtDaemonConfiguration.Builder().setNat64Enabled(true).build()),
+ any(IOtStatusReceiver.class));
+ verify(mFakeOtDaemon, times(1))
+ .setNat64Cidr(eq(TEST_NAT64_CIDR.toString()), any(IOtStatusReceiver.class));
+ }
+
+ @Test
+ public void setConfiguration_enablesNat64_otDaemonRemoteFailure_serviceDoesNotCrash()
+ throws Exception {
+ mService.initialize();
+ mTestLooper.dispatchAll();
+ clearInvocations(mMockRoutingCoordinatorManager, mFakeOtDaemon);
+ mFakeOtDaemon.setSetNat64CidrException(
+ new RemoteException("ot-daemon setNat64Cidr() throws"));
+
+ final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
+ mService.setConfiguration(
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build(), mockReceiver);
+ mTestLooper.dispatchAll();
+
+ verify(mFakeOtDaemon, times(1))
+ .setNat64Cidr(eq(TEST_NAT64_CIDR.toString()), any(IOtStatusReceiver.class));
+ }
+
+ @Test
+ public void setConfiguration_disablesNat64_releasesNat64CidrAndConfiguresOtdaemon()
+ throws Exception {
+ mPersistentSettings.putConfiguration(
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build());
+ mService.initialize();
+ mTestLooper.dispatchAll();
+ clearInvocations(mMockRoutingCoordinatorManager, mFakeOtDaemon);
+
+ final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
+ mService.setConfiguration(
+ new ThreadConfiguration.Builder().setNat64Enabled(false).build(), mockReceiver);
+ mTestLooper.dispatchAll();
+
+ verify(mockReceiver, times(1)).onSuccess();
+ verify(mMockRoutingCoordinatorManager, times(1)).releaseDownstream(any());
+ verify(mMockRoutingCoordinatorManager, never()).requestDownstreamAddress(any());
+ verify(mFakeOtDaemon, times(1))
+ .setConfiguration(
+ eq(new OtDaemonConfiguration.Builder().setNat64Enabled(false).build()),
+ any(IOtStatusReceiver.class));
+ verify(mFakeOtDaemon, times(1)).setNat64Cidr(isNull(), any(IOtStatusReceiver.class));
+ verify(mFakeOtDaemon, never()).setNat64Cidr(isNotNull(), any(IOtStatusReceiver.class));
+ }
+
+ @Test
public void initialize_upstreamNetworkRequestHasCertainTransportTypesAndCapabilities() {
mService.initialize();
mTestLooper.dispatchAll();
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
index af5c9aa..c0e99d7 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
@@ -38,8 +38,11 @@
import android.content.Context;
import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.IConfigurationReceiver;
+import android.net.thread.IOperationReceiver;
import android.net.thread.IOutputReceiver;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
import android.os.Binder;
import android.os.Process;
@@ -320,4 +323,108 @@
inOrder.verify(mOutputWriter).print("Done");
inOrder.verify(mOutputWriter).print("\r\n");
}
+
+ @Test
+ public void config_getConfig_testingPermissionIsChecked() {
+ runShellCommand("config");
+
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
+ }
+
+ @Test
+ public void config_getConfig_serviceTimeOut_failsWithTimeoutError() {
+ runShellCommand("config");
+
+ verify(mControllerService, times(1)).registerConfigurationCallback(any());
+ verify(mErrorWriter, atLeastOnce()).println(contains("timeout"));
+ verify(mOutputWriter, never()).println();
+ }
+
+ @Test
+ public void config_getConfig_expectedValueIsPrinted() {
+ doAnswer(
+ inv -> {
+ ((IConfigurationReceiver) inv.getArgument(0))
+ .onConfigurationChanged(
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .build());
+ return null;
+ })
+ .when(mControllerService)
+ .registerConfigurationCallback(any());
+
+ runShellCommand("config");
+
+ verify(mErrorWriter, never()).println();
+ verify(mOutputWriter, times(1)).println(contains("Nat64Enabled=true"));
+ }
+
+ @Test
+ public void config_setConfig_testingPermissionIsChecked() {
+ runShellCommand("config", "nat64", "enabled");
+
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
+ }
+
+ @Test
+ public void config_setConfig_serviceTimeOut_failedWithTimeoutError() {
+ runShellCommand("config", "nat64", "enabled");
+
+ verify(mControllerService, times(1)).registerConfigurationCallback(any());
+ verify(mErrorWriter, atLeastOnce()).println(contains("timeout"));
+ verify(mOutputWriter, never()).println();
+ }
+
+ @Test
+ public void config_invalidArgument_failsWithInvalidArgumentError() {
+ doAnswer(
+ inv -> {
+ ((IConfigurationReceiver) inv.getArgument(0))
+ .onConfigurationChanged(
+ new ThreadConfiguration.Builder().build());
+ return null;
+ })
+ .when(mControllerService)
+ .registerConfigurationCallback(any());
+
+ runShellCommand("config", "invalidName", "invalidValue");
+
+ verify(mErrorWriter, atLeastOnce()).println(contains("Invalid config"));
+ verify(mOutputWriter, never()).println();
+ }
+
+ @Test
+ public void config_setConfig_expectedValueIsSet() {
+ doAnswer(
+ inv -> {
+ ((IConfigurationReceiver) inv.getArgument(0))
+ .onConfigurationChanged(
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(false)
+ .build());
+ return null;
+ })
+ .when(mControllerService)
+ .registerConfigurationCallback(any());
+ doAnswer(
+ inv -> {
+ ((IOperationReceiver) inv.getArgument(0)).onSuccess();
+ return null;
+ })
+ .when(mControllerService)
+ .setConfiguration(any(), any());
+
+ runShellCommand("config", "nat64", "enabled");
+
+ verify(mControllerService, times(1))
+ .setConfiguration(
+ eq(new ThreadConfiguration.Builder().setNat64Enabled(true).build()), any());
+ verify(mErrorWriter, never()).println();
+ verify(mOutputWriter, never()).println();
+ }
}
diff --git a/tools/Android.bp b/tools/Android.bp
index 2c2ed14..1351eb7 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -81,7 +81,7 @@
"gen_jarjar.py",
"gen_jarjar_test.py",
],
- data: [
+ device_common_data: [
"testdata/test-jarjar-excludes.txt",
// txt with Test classes to test they aren't included when added to jarjar excludes
"testdata/test-jarjar-excludes-testclass.txt",