diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 6f0a4f9744..9086d49231 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -659,7 +659,8 @@ public class ConnectivityDiagnosticsManager { public abstract static class ConnectivityDiagnosticsCallback { /** * Called when the platform completes a data connectivity check. This will also be invoked - * upon registration with the latest report. + * immediately upon registration for each network matching the request with the latest + * report, if a report has already been generated for that network. * *

The Network specified in the ConnectivityReport may not be active any more when this * method is invoked. diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 4e4ff4a63a..9ff7ebee6d 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -16,6 +16,8 @@ package android.net; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -118,7 +120,7 @@ public final class NetworkCapabilities implements Parcelable { mTransportInfo = nc.mTransportInfo; mSignalStrength = nc.mSignalStrength; setUids(nc.mUids); // Will make the defensive copy - setAdministratorUids(nc.mAdministratorUids); + setAdministratorUids(nc.getAdministratorUids()); mOwnerUid = nc.mOwnerUid; mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; mSSID = nc.mSSID; @@ -919,6 +921,9 @@ public final class NetworkCapabilities implements Parcelable { *

For NetworkCapability instances being sent from the System Server, this value MUST be * empty unless the destination is 1) the System Server, or 2) Telephony. In either case, the * receiving entity must have the ACCESS_FINE_LOCATION permission and target R+. + * + *

When received from an app in a NetworkRequest this is always cleared out by the system + * server. This field is never used for matching NetworkRequests to NetworkAgents. */ @NonNull private int[] mAdministratorUids = new int[0]; @@ -927,10 +932,11 @@ public final class NetworkCapabilities implements Parcelable { * *

UIDs included in administratorUids gain administrator privileges over this Network. * Examples of UIDs that should be included in administratorUids are: + * *

* *

In general, user-supplied networks (such as WiFi networks) do not have an administrator. @@ -938,19 +944,34 @@ public final class NetworkCapabilities implements Parcelable { *

An app is granted owner privileges over Networks that it supplies. The owner UID MUST * always be included in administratorUids. * + *

The administrator UIDs are set by network agents. + * * @param administratorUids the UIDs to be set as administrators of this Network. + * @throws IllegalArgumentException if duplicate UIDs are contained in administratorUids + * @see #mAdministratorUids * @hide */ @NonNull public NetworkCapabilities setAdministratorUids(@NonNull final int[] administratorUids) { mAdministratorUids = Arrays.copyOf(administratorUids, administratorUids.length); + Arrays.sort(mAdministratorUids); + for (int i = 0; i < mAdministratorUids.length - 1; i++) { + if (mAdministratorUids[i] >= mAdministratorUids[i + 1]) { + throw new IllegalArgumentException("All administrator UIDs must be unique"); + } + } return this; } /** * Retrieves the UIDs that are administrators of this Network. * + *

This is only populated in NetworkCapabilities objects that come from network agents for + * networks that are managed by specific apps on the system, such as carrier privileged apps or + * wifi suggestion apps. This will include the network owner. + * * @return the int[] of UIDs that are administrators of this Network + * @see #mAdministratorUids * @hide */ @NonNull @@ -960,6 +981,40 @@ public final class NetworkCapabilities implements Parcelable { return Arrays.copyOf(mAdministratorUids, mAdministratorUids.length); } + /** + * Tests if the set of administrator UIDs of this network is the same as that of the passed one. + * + *

The administrator UIDs must be in sorted order. + * + *

nc is assumed non-null. Else, NPE. + * + * @hide + */ + @VisibleForTesting(visibility = PRIVATE) + public boolean equalsAdministratorUids(@NonNull final NetworkCapabilities nc) { + return Arrays.equals(mAdministratorUids, nc.mAdministratorUids); + } + + /** + * Combine the administrator UIDs of the capabilities. + * + *

This is only legal if either of the administrators lists are empty, or if they are equal. + * Combining administrator UIDs is only possible for combining non-overlapping sets of UIDs. + * + *

If both administrator lists are non-empty but not equal, they conflict with each other. In + * this case, it would not make sense to add them together. + */ + private void combineAdministratorUids(@NonNull final NetworkCapabilities nc) { + if (nc.mAdministratorUids.length == 0) return; + if (mAdministratorUids.length == 0) { + mAdministratorUids = Arrays.copyOf(nc.mAdministratorUids, nc.mAdministratorUids.length); + return; + } + if (!equalsAdministratorUids(nc)) { + throw new IllegalStateException("Can't combine two different administrator UID lists"); + } + } + /** * Value indicating that link bandwidth is unspecified. * @hide @@ -1455,6 +1510,7 @@ public final class NetworkCapabilities implements Parcelable { combineUids(nc); combineSSIDs(nc); combineRequestor(nc); + combineAdministratorUids(nc); } /** @@ -1568,7 +1624,8 @@ public final class NetworkCapabilities implements Parcelable { && equalsUids(that) && equalsSSID(that) && equalsPrivateDnsBroken(that) - && equalsRequestor(that); + && equalsRequestor(that) + && equalsAdministratorUids(that); } @Override @@ -1588,7 +1645,8 @@ public final class NetworkCapabilities implements Parcelable { + Objects.hashCode(mTransportInfo) * 41 + Objects.hashCode(mPrivateDnsBroken) * 43 + Objects.hashCode(mRequestorUid) * 47 - + Objects.hashCode(mRequestorPackageName) * 53; + + Objects.hashCode(mRequestorPackageName) * 53 + + Arrays.hashCode(mAdministratorUids) * 59; } @Override @@ -1609,7 +1667,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeArraySet(mUids); dest.writeString(mSSID); dest.writeBoolean(mPrivateDnsBroken); - dest.writeIntArray(mAdministratorUids); + dest.writeIntArray(getAdministratorUids()); dest.writeInt(mOwnerUid); dest.writeInt(mRequestorUid); dest.writeString(mRequestorPackageName); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index d684f0c199..4ab035e7f4 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -653,8 +653,8 @@ public class ConnectivityService extends IConnectivityManager.Stub final MultipathPolicyTracker mMultipathPolicyTracker; @VisibleForTesting - final Map - mConnectivityDiagnosticsCallbacks = new HashMap<>(); + final Map mConnectivityDiagnosticsCallbacks = + new HashMap<>(); /** * Implements support for the legacy "one network per network type" model. @@ -7835,11 +7835,12 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureRunningOnConnectivityServiceThread(); final IConnectivityDiagnosticsCallback cb = cbInfo.mCb; + final IBinder iCb = cb.asBinder(); final NetworkRequestInfo nri = cbInfo.mRequestInfo; // This means that the client registered the same callback multiple times. Do // not override the previous entry, and exit silently. - if (mConnectivityDiagnosticsCallbacks.containsKey(cb)) { + if (mConnectivityDiagnosticsCallbacks.containsKey(iCb)) { if (VDBG) log("Diagnostics callback is already registered"); // Decrement the reference count for this NetworkRequestInfo. The reference count is @@ -7849,40 +7850,75 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - mConnectivityDiagnosticsCallbacks.put(cb, cbInfo); + mConnectivityDiagnosticsCallbacks.put(iCb, cbInfo); try { - cb.asBinder().linkToDeath(cbInfo, 0); + iCb.linkToDeath(cbInfo, 0); } catch (RemoteException e) { cbInfo.binderDied(); + return; + } + + // Once registered, provide ConnectivityReports for matching Networks + final List matchingNetworks = new ArrayList<>(); + synchronized (mNetworkForNetId) { + for (int i = 0; i < mNetworkForNetId.size(); i++) { + final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i); + if (nai.satisfies(nri.request)) { + matchingNetworks.add(nai); + } + } + } + for (final NetworkAgentInfo nai : matchingNetworks) { + final ConnectivityReport report = nai.getConnectivityReport(); + if (report == null) { + continue; + } + if (!checkConnectivityDiagnosticsPermissions( + nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) { + continue; + } + + try { + cb.onConnectivityReportAvailable(report); + } catch (RemoteException e) { + // Exception while sending the ConnectivityReport. Move on to the next network. + } } } private void handleUnregisterConnectivityDiagnosticsCallback( @NonNull IConnectivityDiagnosticsCallback cb, int uid) { ensureRunningOnConnectivityServiceThread(); + final IBinder iCb = cb.asBinder(); - if (!mConnectivityDiagnosticsCallbacks.containsKey(cb)) { + final ConnectivityDiagnosticsCallbackInfo cbInfo = + mConnectivityDiagnosticsCallbacks.remove(iCb); + if (cbInfo == null) { if (VDBG) log("Removing diagnostics callback that is not currently registered"); return; } - final NetworkRequestInfo nri = mConnectivityDiagnosticsCallbacks.get(cb).mRequestInfo; + final NetworkRequestInfo nri = cbInfo.mRequestInfo; if (uid != nri.mUid) { if (VDBG) loge("Different uid than registrant attempting to unregister cb"); return; } - cb.asBinder().unlinkToDeath(mConnectivityDiagnosticsCallbacks.remove(cb), 0); + // Decrement the reference count for this NetworkRequestInfo. The reference count is + // incremented when the NetworkRequestInfo is created as part of + // enforceRequestCountLimit(). + decrementNetworkRequestPerUidCount(nri); + + iCb.unlinkToDeath(cbInfo, 0); } private void handleNetworkTestedWithExtras( @NonNull ConnectivityReportEvent reportEvent, @NonNull PersistableBundle extras) { final NetworkAgentInfo nai = reportEvent.mNai; final NetworkCapabilities networkCapabilities = - new NetworkCapabilities(nai.networkCapabilities); - clearNetworkCapabilitiesUids(networkCapabilities); + getNetworkCapabilitiesWithoutUids(nai.networkCapabilities); final ConnectivityReport report = new ConnectivityReport( reportEvent.mNai.network, @@ -7890,6 +7926,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.linkProperties, networkCapabilities, extras); + nai.setConnectivityReport(report); final List results = getMatchingPermissionedCallbacks(nai); for (final IConnectivityDiagnosticsCallback cb : results) { @@ -7905,8 +7942,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull NetworkAgentInfo nai, long timestampMillis, int detectionMethod, @NonNull PersistableBundle extras) { final NetworkCapabilities networkCapabilities = - new NetworkCapabilities(nai.networkCapabilities); - clearNetworkCapabilitiesUids(networkCapabilities); + getNetworkCapabilitiesWithoutUids(nai.networkCapabilities); final DataStallReport report = new DataStallReport( nai.network, @@ -7939,23 +7975,25 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void clearNetworkCapabilitiesUids(@NonNull NetworkCapabilities nc) { - nc.setUids(null); - nc.setAdministratorUids(new int[0]); - nc.setOwnerUid(Process.INVALID_UID); + private NetworkCapabilities getNetworkCapabilitiesWithoutUids(@NonNull NetworkCapabilities nc) { + final NetworkCapabilities sanitized = new NetworkCapabilities(nc); + sanitized.setUids(null); + sanitized.setAdministratorUids(new int[0]); + sanitized.setOwnerUid(Process.INVALID_UID); + return sanitized; } private List getMatchingPermissionedCallbacks( @NonNull NetworkAgentInfo nai) { final List results = new ArrayList<>(); - for (Entry entry : + for (Entry entry : mConnectivityDiagnosticsCallbacks.entrySet()) { final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue(); final NetworkRequestInfo nri = cbInfo.mRequestInfo; if (nai.satisfies(nri.request)) { if (checkConnectivityDiagnosticsPermissions( nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) { - results.add(entry.getKey()); + results.add(entry.getValue().mCb); } } } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 2f047157d4..15628f03ba 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -16,6 +16,7 @@ package com.android.server.connectivity; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import static android.net.NetworkCapabilities.transportNamesOf; import android.annotation.NonNull; @@ -243,6 +244,10 @@ public class NetworkAgentInfo implements Comparable { // How many of the satisfied requests are of type BACKGROUND_REQUEST. private int mNumBackgroundNetworkRequests = 0; + // The last ConnectivityReport made available for this network. This value is only null before a + // report is generated. Once non-null, it will never be null again. + @Nullable private ConnectivityReport mConnectivityReport; + public final Messenger messenger; public final AsyncChannel asyncChannel; @@ -621,6 +626,30 @@ public class NetworkAgentInfo implements Comparable { for (LingerTimer timer : mLingerTimers) { pw.println(timer); } } + /** + * Sets the most recent ConnectivityReport for this network. + * + *

This should only be called from the ConnectivityService thread. + * + * @hide + */ + public void setConnectivityReport(@NonNull ConnectivityReport connectivityReport) { + mConnectivityReport = connectivityReport; + } + + /** + * Returns the most recent ConnectivityReport for this network, or null if none have been + * reported yet. + * + *

This should only be called from the ConnectivityService thread. + * + * @hide + */ + @Nullable + public ConnectivityReport getConnectivityReport() { + return mConnectivityReport; + } + // TODO: Print shorter members first and only print the boolean variable which value is true // to improve readability. public String toString() { diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index 916c339811..316a83adf4 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -58,6 +58,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; import java.util.Set; @RunWith(AndroidJUnit4.class) @@ -280,6 +281,7 @@ public class NetworkCapabilitiesTest { .addCapability(NET_CAPABILITY_NOT_METERED); if (isAtLeastR()) { netCap.setOwnerUid(123); + netCap.setAdministratorUids(new int[] {5, 11}); } assertParcelingIsLossless(netCap); netCap.setSSID(TEST_SSID); @@ -439,6 +441,23 @@ public class NetworkCapabilitiesTest { return range; } + @Test + public void testSetAdministratorUids() { + NetworkCapabilities nc = + new NetworkCapabilities().setAdministratorUids(new int[] {2, 1, 3}); + + assertArrayEquals(new int[] {1, 2, 3}, nc.getAdministratorUids()); + } + + @Test + public void testSetAdministratorUidsWithDuplicates() { + try { + new NetworkCapabilities().setAdministratorUids(new int[] {1, 1}); + fail("Expected IllegalArgumentException for duplicate uids"); + } catch (IllegalArgumentException expected) { + } + } + @Test public void testCombineCapabilities() { NetworkCapabilities nc1 = new NetworkCapabilities(); @@ -491,6 +510,25 @@ public class NetworkCapabilitiesTest { assertFalse(nc2.appliesToUid(12)); assertTrue(nc1.appliesToUid(22)); assertTrue(nc2.appliesToUid(22)); + + final int[] adminUids = {3, 6, 12}; + nc1.setAdministratorUids(adminUids); + nc2.combineCapabilities(nc1); + assertTrue(nc2.equalsAdministratorUids(nc1)); + assertArrayEquals(nc2.getAdministratorUids(), adminUids); + + final int[] adminUidsOtherOrder = {3, 12, 6}; + nc1.setAdministratorUids(adminUids); + assertTrue(nc2.equalsAdministratorUids(nc1)); + + final int[] adminUids2 = {11, 1, 12, 3, 6}; + nc1.setAdministratorUids(adminUids2); + assertFalse(nc2.equalsAdministratorUids(nc1)); + assertFalse(Arrays.equals(nc2.getAdministratorUids(), adminUids2)); + try { + nc2.combineCapabilities(nc1); + fail("Shouldn't be able to combine different lists of admin UIDs"); + } catch (IllegalStateException expected) { } } @Test diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 86d8a820e4..b02398d1f1 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -317,6 +317,8 @@ public class ConnectivityServiceTest { private static final String TEST_PACKAGE_NAME = "com.android.test.package"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final String INTERFACE_NAME = "interface"; + private MockContext mServiceContext; private HandlerThread mCsHandlerThread; private ConnectivityService mService; @@ -6748,16 +6750,12 @@ public class ConnectivityServiceTest { verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); verify(mConnectivityDiagnosticsCallback).asBinder(); - assertTrue( - mService.mConnectivityDiagnosticsCallbacks.containsKey( - mConnectivityDiagnosticsCallback)); + assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); mService.unregisterConnectivityDiagnosticsCallback(mConnectivityDiagnosticsCallback); verify(mIBinder, timeout(TIMEOUT_MS)) .unlinkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); - assertFalse( - mService.mConnectivityDiagnosticsCallbacks.containsKey( - mConnectivityDiagnosticsCallback)); + assertFalse(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); verify(mConnectivityDiagnosticsCallback, atLeastOnce()).asBinder(); } @@ -6775,9 +6773,7 @@ public class ConnectivityServiceTest { verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); verify(mConnectivityDiagnosticsCallback).asBinder(); - assertTrue( - mService.mConnectivityDiagnosticsCallbacks.containsKey( - mConnectivityDiagnosticsCallback)); + assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); // Register the same callback again mService.registerConnectivityDiagnosticsCallback( @@ -6786,9 +6782,7 @@ public class ConnectivityServiceTest { // Block until all other events are done processing. HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); - assertTrue( - mService.mConnectivityDiagnosticsCallbacks.containsKey( - mConnectivityDiagnosticsCallback)); + assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); } @Test @@ -6915,6 +6909,38 @@ public class ConnectivityServiceTest { mContext.getOpPackageName())); } + @Test + public void testRegisterConnectivityDiagnosticsCallbackCallsOnConnectivityReport() + throws Exception { + // Set up the Network, which leads to a ConnectivityReport being cached for the network. + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, linkProperties); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callback.assertNoCallback(); + + final NetworkRequest request = new NetworkRequest.Builder().build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mConnectivityDiagnosticsCallback) + .onConnectivityReportAvailable(argThat(report -> { + return INTERFACE_NAME.equals(report.getLinkProperties().getInterfaceName()) + && report.getNetworkCapabilities().hasTransport(TRANSPORT_CELLULAR); + })); + } + private void setUpConnectivityDiagnosticsCallback() throws Exception { final NetworkRequest request = new NetworkRequest.Builder().build(); when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);