From fa270db5f704b2397c843dbc33fdf9df152a752f Mon Sep 17 00:00:00 2001 From: James Mattis Date: Mon, 31 May 2021 17:11:10 -0700 Subject: [PATCH] Update CS so that per-app OEM APIs can be tested Updates to ConnectivityService so that the set OEM network preference per app APIs can be tested via CTS. Bug: 176496580 Bug: 176494815 Test: atest FrameworksNetTests atest FrameworksNetIntegrationTests atest CtsNetTestCasesLatestSdk Change-Id: I5a47dcece31749293f080af060218d827082eb67 --- .../android/server/ConnectivityService.java | 77 ++++++++++-- .../server/ConnectivityServiceTest.java | 113 +++++++++++++++++- 2 files changed, 172 insertions(+), 18 deletions(-) diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 15b666a6eb..55aa56bece 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -79,6 +79,8 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY; import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired; import static android.os.Process.INVALID_UID; import static android.os.Process.VPN_UID; @@ -2596,6 +2598,12 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + private void enforceManageTestNetworksPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_TEST_NETWORKS, + "ConnectivityService"); + } + private boolean checkNetworkStackPermission() { return checkAnyPermissionOf( android.Manifest.permission.NETWORK_STACK, @@ -9876,8 +9884,15 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull final OemNetworkPreferences preference, @Nullable final IOnCompleteListener listener) { - enforceAutomotiveDevice(); - enforceOemNetworkPreferencesPermission(); + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + // Only bypass the permission/device checks if this is a valid test request. + if (isValidTestOemNetworkPreference(preference)) { + enforceManageTestNetworksPermission(); + } else { + enforceAutomotiveDevice(); + enforceOemNetworkPreferencesPermission(); + validateOemNetworkPreferences(preference); + } // TODO: Have a priority for each preference. if (!mProfileNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) { @@ -9889,18 +9904,41 @@ public class ConnectivityService extends IConnectivityManager.Stub throwConcurrentPreferenceException(); } - Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); - validateOemNetworkPreferences(preference); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE, new Pair<>(preference, listener))); } + /** + * Check the validity of an OEM network preference to be used for testing purposes. + * @param preference the preference to validate + * @return true if this is a valid OEM network preference test request. + */ + private boolean isValidTestOemNetworkPreference( + @NonNull final OemNetworkPreferences preference) { + // Allow for clearing of an existing OemNetworkPreference used for testing. + // This isn't called on the handler thread so it is possible that mOemNetworkPreferences + // changes after this check is complete. This is an unlikely scenario as calling of this API + // is controlled by the OEM therefore the added complexity is not worth adding given those + // circumstances. That said, it is an edge case to be aware of hence this comment. + final boolean isValidTestClearPref = preference.getNetworkPreferences().size() == 0 + && isTestOemNetworkPreference(mOemNetworkPreferences); + return isTestOemNetworkPreference(preference) || isValidTestClearPref; + } + + private boolean isTestOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { + final Map prefMap = preference.getNetworkPreferences(); + return prefMap.size() == 1 + && (prefMap.containsValue(OEM_NETWORK_PREFERENCE_TEST) + || prefMap.containsValue(OEM_NETWORK_PREFERENCE_TEST_ONLY)); + } + private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) { for (@OemNetworkPreferences.OemNetworkPreference final int pref : preference.getNetworkPreferences().values()) { - if (OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED == pref) { - final String msg = "OEM_NETWORK_PREFERENCE_UNINITIALIZED is an invalid value."; - throw new IllegalArgumentException(msg); + if (pref <= 0 || OemNetworkPreferences.OEM_NETWORK_PREFERENCE_MAX < pref) { + throw new IllegalArgumentException( + OemNetworkPreferences.oemNetworkPreferenceToString(pref) + + " is an invalid value."); } } } @@ -10124,13 +10162,21 @@ public class ConnectivityService extends IConnectivityManager.Stub case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY: requests.add(createOemPrivateNetworkRequest()); break; + case OEM_NETWORK_PREFERENCE_TEST: + requests.add(createUnmeteredNetworkRequest()); + requests.add(createTestNetworkRequest()); + requests.add(createDefaultRequest()); + break; + case OEM_NETWORK_PREFERENCE_TEST_ONLY: + requests.add(createTestNetworkRequest()); + break; default: // This should never happen. throw new IllegalArgumentException("createNriFromOemNetworkPreferences()" + " called with invalid preference of " + preference); } - final ArraySet ranges = new ArraySet(); + final ArraySet ranges = new ArraySet<>(); for (final int uid : uids) { ranges.add(new UidRange(uid, uid)); } @@ -10162,10 +10208,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } private NetworkCapabilities createDefaultPerAppNetCap() { - final NetworkCapabilities netCap = new NetworkCapabilities(); - netCap.addCapability(NET_CAPABILITY_INTERNET); - netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); - return netCap; + final NetworkCapabilities netcap = new NetworkCapabilities(); + netcap.addCapability(NET_CAPABILITY_INTERNET); + netcap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); + return netcap; + } + + private NetworkRequest createTestNetworkRequest() { + final NetworkCapabilities netcap = new NetworkCapabilities(); + netcap.clearAll(); + netcap.addTransportType(TRANSPORT_TEST); + return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap); } } } diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 2c19cb84a0..11cf2f32bc 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -113,6 +113,8 @@ import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED; @@ -10631,8 +10633,7 @@ public class ConnectivityServiceTest { } @Test - public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError() - throws PackageManager.NameNotFoundException { + public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError() { @OemNetworkPreferences.OemNetworkPreference final int prefToTest = OEM_NETWORK_PREFERENCE_UNINITIALIZED; @@ -10894,7 +10895,48 @@ public class ConnectivityServiceTest { assertThrows(UnsupportedOperationException.class, () -> mService.setOemNetworkPreference( createDefaultOemNetworkPreferences(networkPref), - new TestOemListenerCallback())); + null)); + } + + @Test + public void testSetOemNetworkPreferenceFailsForTestRequestWithoutPermission() { + // Calling setOemNetworkPreference() with a test pref requires the permission + // MANAGE_TEST_NETWORKS. + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false); + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_TEST; + + // Act on ConnectivityService.setOemNetworkPreference() + assertThrows(SecurityException.class, + () -> mService.setOemNetworkPreference( + createDefaultOemNetworkPreferences(networkPref), + null)); + } + + @Test + public void testSetOemNetworkPreferenceFailsForInvalidTestRequest() { + assertSetOemNetworkPreferenceFailsForInvalidTestRequest(OEM_NETWORK_PREFERENCE_TEST); + } + + @Test + public void testSetOemNetworkPreferenceFailsForInvalidTestOnlyRequest() { + assertSetOemNetworkPreferenceFailsForInvalidTestRequest(OEM_NETWORK_PREFERENCE_TEST_ONLY); + } + + private void assertSetOemNetworkPreferenceFailsForInvalidTestRequest( + @OemNetworkPreferences.OemNetworkPreference final int oemNetworkPreferenceForTest) { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); + final String secondPackage = "does.not.matter"; + + // A valid test request would only have a single mapping. + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, oemNetworkPreferenceForTest) + .addNetworkPreference(secondPackage, oemNetworkPreferenceForTest) + .build(); + + // Act on ConnectivityService.setOemNetworkPreference() + assertThrows(IllegalArgumentException.class, + () -> mService.setOemNetworkPreference(pref, null)); } private void setOemNetworkPreferenceAgentConnected(final int transportType, @@ -11060,8 +11102,18 @@ public class ConnectivityServiceTest { private void setupSetOemNetworkPreferenceForPreferenceTest( @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, @NonNull final UidRangeParcel[] uidRanges, - @NonNull final String testPackageName) - throws Exception { + @NonNull final String testPackageName) throws Exception { + setupSetOemNetworkPreferenceForPreferenceTest( + networkPrefToSetup, uidRanges, testPackageName, true); + } + + private void setupSetOemNetworkPreferenceForPreferenceTest( + @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, + @NonNull final UidRangeParcel[] uidRanges, + @NonNull final String testPackageName, + final boolean hasAutomotiveFeature) throws Exception { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, hasAutomotiveFeature); + // These tests work off a single UID therefore using 'start' is valid. mockGetApplicationInfo(testPackageName, uidRanges[0].start); @@ -11365,6 +11417,55 @@ public class ConnectivityServiceTest { reset(mMockNetd); } + /** + * Test the tracked default requests allows test requests without standard setup. + */ + @Test + public void testSetOemNetworkPreferenceAllowsValidTestRequestWithoutChecks() throws Exception { + @OemNetworkPreferences.OemNetworkPreference int networkPref = + OEM_NETWORK_PREFERENCE_TEST; + validateSetOemNetworkPreferenceAllowsValidTestPrefRequest(networkPref); + } + + /** + * Test the tracked default requests allows test only requests without standard setup. + */ + @Test + public void testSetOemNetworkPreferenceAllowsValidTestOnlyRequestWithoutChecks() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference int networkPref = + OEM_NETWORK_PREFERENCE_TEST_ONLY; + validateSetOemNetworkPreferenceAllowsValidTestPrefRequest(networkPref); + } + + private void validateSetOemNetworkPreferenceAllowsValidTestPrefRequest(int networkPref) + throws Exception { + // The caller must have the MANAGE_TEST_NETWORKS permission. + final int testPackageUid = 123; + final String validTestPackageName = "does.not.matter"; + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels(uidRangesForUids(testPackageUid)); + mServiceContext.setPermission( + Manifest.permission.MANAGE_TEST_NETWORKS, PERMISSION_GRANTED); + + // Put the system into a state in which setOemNetworkPreference() would normally fail. This + // will confirm that a valid test request can bypass these checks. + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false); + mServiceContext.setPermission( + Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE, PERMISSION_DENIED); + + // Validate the starting requests only includes the system default request. + assertEquals(1, mService.mDefaultNetworkRequests.size()); + + // Add an OEM default network request to track. + setupSetOemNetworkPreferenceForPreferenceTest( + networkPref, uidRanges, validTestPackageName, + false /* hasAutomotiveFeature */); + + // Two requests should now exist; the system default and the test request. + assertEquals(2, mService.mDefaultNetworkRequests.size()); + } + /** * Test the tracked default requests clear previous OEM requests on setOemNetworkPreference(). */ @@ -11377,7 +11478,7 @@ public class ConnectivityServiceTest { final UidRangeParcel[] uidRanges = toUidRangeStableParcels(uidRangesForUids(testPackageUid)); - // Validate the starting requests only includes the fallback request. + // Validate the starting requests only includes the system default request. assertEquals(1, mService.mDefaultNetworkRequests.size()); // Add an OEM default network request to track.