diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index ed9df56f6d..ec71d3d65a 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -1584,6 +1584,28 @@ public final class NetworkCapabilities implements Parcelable { return false; } + /** + * Compare if the given NetworkCapabilities have the same UIDs. + * + * @hide + */ + public static boolean hasSameUids(@Nullable NetworkCapabilities nc1, + @Nullable NetworkCapabilities nc2) { + final Set uids1 = (nc1 == null) ? null : nc1.mUids; + final Set uids2 = (nc2 == null) ? null : nc2.mUids; + if (null == uids1) return null == uids2; + if (null == uids2) return false; + // Make a copy so it can be mutated to check that all ranges in uids2 also are in uids. + final Set uids = new ArraySet<>(uids2); + for (UidRange range : uids1) { + if (!uids.contains(range)) { + return false; + } + uids.remove(range); + } + return uids.isEmpty(); + } + /** * Tests if the set of UIDs that this network applies to is the same as the passed network. *

@@ -1600,19 +1622,7 @@ public final class NetworkCapabilities implements Parcelable { */ @VisibleForTesting public boolean equalsUids(@NonNull NetworkCapabilities nc) { - Set comparedUids = nc.mUids; - if (null == comparedUids) return null == mUids; - if (null == mUids) return false; - // Make a copy so it can be mutated to check that all ranges in mUids - // also are in uids. - final Set uids = new ArraySet<>(mUids); - for (UidRange range : comparedUids) { - if (!uids.contains(range)) { - return false; - } - uids.remove(range); - } - return uids.isEmpty(); + return hasSameUids(nc, this); } /** diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 48ae7a0a64..899286b819 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -7348,6 +7348,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager.updateTransportsForNetwork( nai.network.getNetId(), newNc.getTransportTypes()); } + + maybeSendProxyBroadcast(nai, prevNc, newNc); } /** Convenience method to update the capabilities for a given network. */ @@ -7440,6 +7442,30 @@ public class ConnectivityService extends IConnectivityManager.Stub maybeCloseSockets(nai, ranges, exemptUids); } + private boolean isProxySetOnAnyDefaultNetwork() { + ensureRunningOnConnectivityServiceThread(); + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + final NetworkAgentInfo nai = nri.getSatisfier(); + if (nai != null && nai.linkProperties.getHttpProxy() != null) { + return true; + } + } + return false; + } + + private void maybeSendProxyBroadcast(NetworkAgentInfo nai, NetworkCapabilities prevNc, + NetworkCapabilities newNc) { + // When the apps moved from/to a VPN, a proxy broadcast is needed to inform the apps that + // the proxy might be changed since the default network satisfied by the apps might also + // changed. + // TODO: Try to track the default network that apps use and only send a proxy broadcast when + // that happens to prevent false alarms. + if (nai.isVPN() && nai.everConnected && !NetworkCapabilities.hasSameUids(prevNc, newNc) + && (nai.linkProperties.getHttpProxy() != null || isProxySetOnAnyDefaultNetwork())) { + mProxyTracker.sendProxyBroadcast(); + } + } + private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc, NetworkCapabilities newNc) { Set prevRanges = null == prevNc ? null : prevNc.getUidRanges(); diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 4530650fbe..be7239d3f9 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -486,6 +486,7 @@ public class ConnectivityServiceTest { @Mock VpnProfileStore mVpnProfileStore; @Mock SystemConfigManager mSystemConfigManager; @Mock Resources mResources; + @Mock ProxyTracker mProxyTracker; private ArgumentCaptor mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -1280,10 +1281,14 @@ public class ConnectivityServiceTest { return mMockNetworkAgent; } - public void establish(LinkProperties lp, int uid, Set ranges, boolean validated, - boolean hasInternet, boolean isStrictMode) throws Exception { + private void setOwnerAndAdminUid(int uid) throws Exception { mNetworkCapabilities.setOwnerUid(uid); mNetworkCapabilities.setAdministratorUids(new int[]{uid}); + } + + public void establish(LinkProperties lp, int uid, Set ranges, boolean validated, + boolean hasInternet, boolean isStrictMode) throws Exception { + setOwnerAndAdminUid(uid); registerAgent(false, ranges, lp); connect(validated, hasInternet, isStrictMode); waitForIdle(); @@ -1638,7 +1643,7 @@ public class ConnectivityServiceTest { doReturn(mNetIdManager).when(deps).makeNetIdManager(); doReturn(mNetworkStack).when(deps).getNetworkStack(); doReturn(mSystemProperties).when(deps).getSystemProperties(); - doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); + doReturn(mProxyTracker).when(deps).makeProxyTracker(any(), any()); doReturn(true).when(deps).queryUserAccess(anyInt(), any(), any()); doAnswer(inv -> { mPolicyTracker = new WrappedMultinetworkPolicyTracker( @@ -10382,16 +10387,23 @@ public class ConnectivityServiceTest { @Test public void testVpnUidRangesUpdate() throws Exception { - LinkProperties lp = new LinkProperties(); + // Set up a WiFi network without proxy. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + assertNull(mService.getProxyForNetwork(null)); + assertNull(mCm.getDefaultProxy()); + + final LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); final UidRange vpnRange = PRIMARY_UIDRANGE; - Set vpnRanges = Collections.singleton(vpnRange); + final Set vpnRanges = Collections.singleton(vpnRange); mMockVpn.establish(lp, VPN_UID, vpnRanges); assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); + // VPN is connected but proxy is not set, so there is no need to send proxy broadcast. + verify(mProxyTracker, never()).sendProxyBroadcast(); - reset(mMockNetd); // Update to new range which is old range minus APP1, i.e. only APP2 final Set newRanges = new HashSet<>(Arrays.asList( new UidRange(vpnRange.start, APP1_UID - 1), @@ -10401,6 +10413,101 @@ public class ConnectivityServiceTest { assertVpnUidRangesUpdated(true, newRanges, VPN_UID); assertVpnUidRangesUpdated(false, vpnRanges, VPN_UID); + + // Uid has changed but proxy is not set, so there is no need to send proxy broadcast. + verify(mProxyTracker, never()).sendProxyBroadcast(); + + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + lp.setHttpProxy(testProxyInfo); + mMockVpn.sendLinkProperties(lp); + waitForIdle(); + // Proxy is set, so send a proxy broadcast. + verify(mProxyTracker, times(1)).sendProxyBroadcast(); + reset(mProxyTracker); + + mMockVpn.setUids(vpnRanges); + waitForIdle(); + // Uid has changed and proxy is already set, so send a proxy broadcast. + verify(mProxyTracker, times(1)).sendProxyBroadcast(); + reset(mProxyTracker); + + // Proxy is removed, send a proxy broadcast. + lp.setHttpProxy(null); + mMockVpn.sendLinkProperties(lp); + waitForIdle(); + verify(mProxyTracker, times(1)).sendProxyBroadcast(); + reset(mProxyTracker); + + // Proxy is added in WiFi(default network), setDefaultProxy will be called. + final LinkProperties wifiLp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork()); + assertNotNull(wifiLp); + wifiLp.setHttpProxy(testProxyInfo); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + waitForIdle(); + verify(mProxyTracker, times(1)).setDefaultProxy(eq(testProxyInfo)); + reset(mProxyTracker); + } + + @Test + public void testProxyBroadcastWillBeSentWhenVpnHasProxyAndConnects() throws Exception { + // Set up a WiFi network without proxy. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + assertNull(mService.getProxyForNetwork(null)); + assertNull(mCm.getDefaultProxy()); + + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + lp.setHttpProxy(testProxyInfo); + final UidRange vpnRange = PRIMARY_UIDRANGE; + final Set vpnRanges = Collections.singleton(vpnRange); + mMockVpn.setOwnerAndAdminUid(VPN_UID); + mMockVpn.registerAgent(false, vpnRanges, lp); + // In any case, the proxy broadcast won't be sent before VPN goes into CONNECTED state. + // Otherwise, the app that calls ConnectivityManager#getDefaultProxy() when it receives the + // proxy broadcast will get null. + verify(mProxyTracker, never()).sendProxyBroadcast(); + mMockVpn.connect(true /* validated */, true /* hasInternet */, false /* isStrictMode */); + waitForIdle(); + assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); + // Vpn is connected with proxy, so the proxy broadcast will be sent to inform the apps to + // update their proxy data. + verify(mProxyTracker, times(1)).sendProxyBroadcast(); + } + + @Test + public void testProxyBroadcastWillBeSentWhenTheProxyOfNonDefaultNetworkHasChanged() + throws Exception { + // Set up a CELLULAR network without proxy. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + assertNull(mService.getProxyForNetwork(null)); + assertNull(mCm.getDefaultProxy()); + // CELLULAR network should be the default network. + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Set up a WiFi network without proxy. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + assertNull(mService.getProxyForNetwork(null)); + assertNull(mCm.getDefaultProxy()); + // WiFi network should be the default network. + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + // CELLULAR network is not the default network. + assertNotEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // CELLULAR network is not the system default network, but it might be a per-app default + // network. The proxy broadcast should be sent once its proxy has changed. + final LinkProperties cellularLp = new LinkProperties(); + cellularLp.setInterfaceName(MOBILE_IFNAME); + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + cellularLp.setHttpProxy(testProxyInfo); + mCellNetworkAgent.sendLinkProperties(cellularLp); + waitForIdle(); + verify(mProxyTracker, times(1)).sendProxyBroadcast(); } @Test