Merge "Send a proxy broadcast when apps moved from/to a VPN" into sc-dev
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index ed9df56..ec71d3d 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -1585,6 +1585,28 @@
}
/**
+ * Compare if the given NetworkCapabilities have the same UIDs.
+ *
+ * @hide
+ */
+ public static boolean hasSameUids(@Nullable NetworkCapabilities nc1,
+ @Nullable NetworkCapabilities nc2) {
+ final Set<UidRange> uids1 = (nc1 == null) ? null : nc1.mUids;
+ final Set<UidRange> 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<UidRange> 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.
* <p>
* This test only checks whether equal range objects are in both sets. It will
@@ -1600,19 +1622,7 @@
*/
@VisibleForTesting
public boolean equalsUids(@NonNull NetworkCapabilities nc) {
- Set<UidRange> 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<UidRange> 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 48ae7a0..899286b 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -7348,6 +7348,8 @@
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 @@
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<UidRange> 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 4530650..be7239d 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -486,6 +486,7 @@
@Mock VpnProfileStore mVpnProfileStore;
@Mock SystemConfigManager mSystemConfigManager;
@Mock Resources mResources;
+ @Mock ProxyTracker mProxyTracker;
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -1280,10 +1281,14 @@
return mMockNetworkAgent;
}
- public void establish(LinkProperties lp, int uid, Set<UidRange> 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<UidRange> ranges, boolean validated,
+ boolean hasInternet, boolean isStrictMode) throws Exception {
+ setOwnerAndAdminUid(uid);
registerAgent(false, ranges, lp);
connect(validated, hasInternet, isStrictMode);
waitForIdle();
@@ -1638,7 +1643,7 @@
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 @@
@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<UidRange> vpnRanges = Collections.singleton(vpnRange);
+ final Set<UidRange> 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<UidRange> newRanges = new HashSet<>(Arrays.asList(
new UidRange(vpnRange.start, APP1_UID - 1),
@@ -10401,6 +10413,101 @@
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<UidRange> 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