Merge "[Thread] request/release the NAT64 CIDR" into main
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/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/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/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/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 665e6f9..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);
}
@@ -10267,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()) {
@@ -10278,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);
@@ -10286,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
@@ -10293,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() {
@@ -10424,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 ?
@@ -10788,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);
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/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/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/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/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java b/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
index 295acb8..4354702 100644
--- a/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
+++ b/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
@@ -220,7 +220,7 @@
public void setNat64EnabledAndWait(boolean enabled) throws Exception {
final ThreadConfiguration config = getConfiguration();
final ThreadConfiguration newConfig =
- new ThreadConfiguration.Builder().setNat64Enabled(enabled).build();
+ new ThreadConfiguration.Builder(config).setNat64Enabled(enabled).build();
setConfigurationAndWait(newConfig);
}