From 9f38060e3099d9afba394d891d2abb1ee0633506 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Wed, 10 Feb 2021 17:07:50 +0900 Subject: [PATCH 1/5] Minor fixes to VpnTransportInfo. This CL addesses comments on aosp/1570921. Bug: 173331190 Test: new test coverage Change-Id: Ie8e1bd63bb818a03f4b17402e1b365577ca034a2 --- .../java/android/net/VpnTransportInfoTest.java | 2 -- .../android/server/ConnectivityServiceTest.java | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java index 2fd5e3861c..866f38c843 100644 --- a/tests/net/java/android/net/VpnTransportInfoTest.java +++ b/tests/net/java/android/net/VpnTransportInfoTest.java @@ -17,7 +17,6 @@ package android.net; import static com.android.testutils.ParcelUtils.assertParcelSane; -import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -36,7 +35,6 @@ public class VpnTransportInfoTest { public void testParceling() { VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM); assertParcelSane(v, 1 /* fieldCount */); - assertParcelingIsLossless(v); } @Test diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 0dce9e10e0..7905f577d1 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -200,6 +200,7 @@ import android.net.ResolverParamsParcel; import android.net.RouteInfo; import android.net.RouteInfoParcel; import android.net.SocketKeepalive; +import android.net.TransportInfo; import android.net.UidRange; import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; @@ -1266,6 +1267,15 @@ public class ConnectivityServiceTest { } } + private void assertVpnTransportInfo(NetworkCapabilities nc, int type) { + assertNotNull(nc); + final TransportInfo ti = nc.getTransportInfo(); + assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti, + ti instanceof VpnTransportInfo); + assertEquals(type, ((VpnTransportInfo) ti).type); + + } + private void processBroadcastForVpn(Intent intent) { // The BroadcastReceiver for this broadcast checks it is being run on the handler thread. final Handler handler = new Handler(mCsHandlerThread.getLooper()); @@ -6410,6 +6420,8 @@ public class ConnectivityServiceTest { assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED)); assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); } private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) { @@ -6449,6 +6461,7 @@ public class ConnectivityServiceTest { assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); // A VPN without underlying networks is not suspended. assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); final int userId = UserHandle.getUserId(Process.myUid()); assertDefaultNetworkCapabilities(userId /* no networks */); @@ -6612,6 +6625,7 @@ public class ConnectivityServiceTest { // By default, VPN is set to track default network (i.e. its underlying networks is null). // In case of no default network, VPN is considered metered. assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); // Connect to Cell; Cell is the default network. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); @@ -6669,6 +6683,7 @@ public class ConnectivityServiceTest { NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertNotNull("nc=" + nc, nc.getUids()); assertEquals(nc.getUids(), uidRangesForUid(uid)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); // Set an underlying network and expect to see the VPN transports change. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); From bcd692fdc9591522e3f8716a459eb1b73b806c87 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Fri, 15 Jan 2021 01:29:01 +0900 Subject: [PATCH 2/5] Convert LockdownVpnTracker to NetworkCallbacks. This will allow moving LockdownVpnTracker from the connectivity to the VPN code. This requires moderate refactoring since it's pretty tightly coupled to both. In this CL: 1. Add an @hide API to tell ConnectivityService that legacy lockdown VPN is enabled. I chose not to use the existing setVpnRequiredForUids API because that method has specific semantics and because it will be required long term since it's used by non-legacy VPN types. 2. Instead of updating LockdownVpnTracker inline from the ConnectivityService handler thread, have it listen to NetworkCallbacks. This introduces an extra thread hop, but most of the interactions between the lockdown VPN and CS were via NetworkAgent, which is asynchronous anyway. 3. Add code to LegacyTypeTracker to send the extra CONNECTIVITY_ACTION broadcast for the underlying network type that is sent after the VPN connects. In order to do this, make Make LockdownVpnTracker specify its underlying network (via setUnderlyingNetworks) when it connects. 4. Reimplement LockdownVpnTracker#augmentNetworkInfo based on information that is available in ConnectivityService. 5. Remove the code in LockdownVpnTracker that counted errors. I think this code has not worked since lollipop, because ConnectivityService never sees NetworkInfo objects in state FAILED. This is because ConnectivityService only hears about NetworkInfo objects via NetworkAgents, and LegacyVpnRunner only registers its NetworkAgent when the connection succeeds. Bug: 173331190 Test: passes existing tests in ConnectivityServiceTest Change-Id: I66d18512882efd468ee0ecec61f28786a195b357 --- .../src/android/net/ConnectivityManager.java | 39 ++++ .../src/android/net/IConnectivityManager.aidl | 1 + .../android/server/ConnectivityService.java | 184 +++++++++++------- .../server/ConnectivityServiceTest.java | 37 ++-- 4 files changed, 180 insertions(+), 81 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 0976b753e6..8437798b7b 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1220,6 +1220,45 @@ public class ConnectivityManager { } } + /** + * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by + * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12 + * but is still supported for backwards compatibility. + *

+ * This type of VPN is assumed always to use the system default network, and must always declare + * exactly one underlying network, which is the network that was the default when the VPN + * connected. + *

+ * Calling this method with {@code true} enables legacy behaviour, specifically: + *

+ * + * @param enabled whether legacy lockdown VPN is enabled or disabled + * + * TODO: @SystemApi(client = MODULE_LIBRARIES) + * + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void setLegacyLockdownVpnEnabled(boolean enabled) { + try { + mService.setLegacyLockdownVpnEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Returns details about the currently active default data network * for a given uid. This is for internal use only to avoid spying diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index f909d13625..ab134eb6d2 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -151,6 +151,7 @@ interface IConnectivityManager boolean isVpnLockdownEnabled(int userId); List getVpnLockdownWhitelist(int userId); void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); + void setLegacyLockdownVpnEnabled(boolean enabled); void setProvisioningNotificationVisible(boolean visible, int networkType, in String action); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 42b6a7f1aa..84aaca0ae2 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -318,7 +318,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by // a direct call to LockdownVpnTracker.isEnabled(). @GuardedBy("mVpns") - private boolean mLockdownEnabled; + private volatile boolean mLockdownEnabled; @GuardedBy("mVpns") private LockdownVpnTracker mLockdownTracker; @@ -755,6 +755,27 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + // When a lockdown VPN connects, send another CONNECTED broadcast for the underlying + // network type, to preserve previous behaviour. + private void maybeSendLegacyLockdownBroadcast(@NonNull NetworkAgentInfo vpnNai) { + if (vpnNai != mService.getLegacyLockdownNai()) return; + + if (vpnNai.declaredUnderlyingNetworks == null + || vpnNai.declaredUnderlyingNetworks.length != 1) { + Log.wtf(TAG, "Legacy lockdown VPN must have exactly one underlying network: " + + Arrays.toString(vpnNai.declaredUnderlyingNetworks)); + return; + } + final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork( + vpnNai.declaredUnderlyingNetworks[0]); + if (underlyingNai == null) return; + + final int type = underlyingNai.networkInfo.getType(); + final DetailedState state = DetailedState.CONNECTED; + maybeLogBroadcast(underlyingNai, state, type, true /* isDefaultNetwork */); + mService.sendLegacyNetworkBroadcast(underlyingNai, state, type); + } + /** Adds the given network to the specified legacy type list. */ public void add(int type, NetworkAgentInfo nai) { if (!isTypeSupported(type)) { @@ -772,9 +793,17 @@ public class ConnectivityService extends IConnectivityManager.Stub // Send a broadcast if this is the first network of its type or if it's the default. final boolean isDefaultNetwork = mService.isDefaultNetwork(nai); + + // If a legacy lockdown VPN is active, override the NetworkInfo state in all broadcasts + // to preserve previous behaviour. + final DetailedState state = mService.getLegacyLockdownState(DetailedState.CONNECTED); if ((list.size() == 1) || isDefaultNetwork) { - maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork); - mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type); + maybeLogBroadcast(nai, state, type, isDefaultNetwork); + mService.sendLegacyNetworkBroadcast(nai, state, type); + } + + if (type == TYPE_VPN && state == DetailedState.CONNECTED) { + maybeSendLegacyLockdownBroadcast(nai); } } @@ -1474,11 +1503,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) { networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); } - synchronized (mVpns) { - if (mLockdownTracker != null) { - mLockdownTracker.augmentNetworkInfo(networkInfo); - } - } + networkInfo.setDetailedState( + getLegacyLockdownState(networkInfo.getDetailedState()), + "" /* reason */, null /* extraInfo */); } /** @@ -1537,14 +1564,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return nai.network; } - // Public because it's used by mLockdownTracker. - public NetworkInfo getActiveNetworkInfoUnfiltered() { - enforceAccessPermission(); - final int uid = mDeps.getCallingUid(); - NetworkState state = getUnfilteredActiveNetworkState(uid); - return state.networkInfo; - } - @Override public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { NetworkStack.checkNetworkStackPermission(mContext); @@ -2340,13 +2359,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { - synchronized (mVpns) { - if (mLockdownTracker != null) { - info = new NetworkInfo(info); - mLockdownTracker.augmentNetworkInfo(info); - } - } - Intent intent = new Intent(bcastType); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info)); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); @@ -2887,7 +2899,15 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.wtf(TAG, "Non-virtual networks cannot have underlying networks"); break; } + final List underlying = (List) arg.second; + + if (isLegacyLockdownNai(nai) + && (underlying == null || underlying.size() != 1)) { + Log.wtf(TAG, "Legacy lockdown VPN " + nai.toShortString() + + " must have exactly one underlying network: " + underlying); + } + final Network[] oldUnderlying = nai.declaredUnderlyingNetworks; nai.declaredUnderlyingNetworks = (underlying != null) ? underlying.toArray(new Network[0]) : null; @@ -3496,7 +3516,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // incorrect) behavior. mNetworkActivityTracker.updateDataActivityTracking( null /* newNetwork */, nai); - notifyLockdownVpn(nai); ensureNetworkTransitionWakelock(nai.toShortString()); } } @@ -5071,10 +5090,59 @@ public class ConnectivityService extends IConnectivityManager.Stub mVpnBlockedUidRanges = newVpnBlockedUidRanges; } + @Override + public void setLegacyLockdownVpnEnabled(boolean enabled) { + enforceSettingsPermission(); + mHandler.post(() -> mLockdownEnabled = enabled); + } + + // TODO: remove when the VPN code moves out. private boolean isLockdownVpnEnabled() { return mKeyStore.contains(Credentials.LOCKDOWN_VPN); } + private boolean isLegacyLockdownNai(NetworkAgentInfo nai) { + return mLockdownEnabled + && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY + && nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID); + } + + private NetworkAgentInfo getLegacyLockdownNai() { + if (!mLockdownEnabled) { + return null; + } + // The legacy lockdown VPN always only applies to UID 0. + final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID); + if (nai == null || !isLegacyLockdownNai(nai)) return null; + + // The legacy lockdown VPN must always have exactly one underlying network. + if (nai.declaredUnderlyingNetworks == null || nai.declaredUnderlyingNetworks.length != 1) { + return null; + } + + // The legacy lockdown VPN always uses the default network. + // If the VPN's underlying network is no longer the current default network, it means that + // the default network has just switched, and the VPN is about to disconnect. + // Report that the VPN is not connected, so when the state of NetworkInfo objects + // overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED. + final NetworkAgentInfo defaultNetwork = getDefaultNetwork(); + if (defaultNetwork == null + || !defaultNetwork.network.equals(nai.declaredUnderlyingNetworks[0])) { + return null; + } + + return nai; + }; + + private DetailedState getLegacyLockdownState(DetailedState origState) { + if (origState != DetailedState.CONNECTED) { + return origState; + } + return (mLockdownEnabled && getLegacyLockdownNai() == null) + ? DetailedState.CONNECTING + : DetailedState.CONNECTED; + } + @Override public boolean updateLockdownVpn() { // Allow the system UID for the system server and for Settings. @@ -5087,32 +5155,32 @@ public class ConnectivityService extends IConnectivityManager.Stub synchronized (mVpns) { // Tear down existing lockdown if profile was removed - mLockdownEnabled = isLockdownVpnEnabled(); - if (mLockdownEnabled) { - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); - if (profileTag == null) { - loge("Lockdown VPN configured but cannot be read from keystore"); - return false; - } - String profileName = new String(profileTag); - final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); - if (profile == null) { - loge("Lockdown VPN configured invalid profile " + profileName); - setLockdownTracker(null); - return true; - } - int user = UserHandle.getUserId(mDeps.getCallingUid()); - Vpn vpn = mVpns.get(user); - if (vpn == null) { - logw("VPN for user " + user + " not ready yet. Skipping lockdown"); - return false; - } - setLockdownTracker( - new LockdownVpnTracker(mContext, this, mHandler, mKeyStore, vpn, profile)); - } else { + if (!isLockdownVpnEnabled()) { setLockdownTracker(null); + return true; } + + byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); + if (profileTag == null) { + loge("Lockdown VPN configured but cannot be read from keystore"); + return false; + } + String profileName = new String(profileTag); + final VpnProfile profile = VpnProfile.decode( + profileName, mKeyStore.get(Credentials.VPN + profileName)); + if (profile == null) { + loge("Lockdown VPN configured invalid profile " + profileName); + setLockdownTracker(null); + return true; + } + int user = UserHandle.getUserId(mDeps.getCallingUid()); + Vpn vpn = mVpns.get(user); + if (vpn == null) { + logw("VPN for user " + user + " not ready yet. Skipping lockdown"); + return false; + } + setLockdownTracker( + new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile)); } return true; @@ -7341,7 +7409,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); } mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork); - notifyLockdownVpn(newDefaultNetwork); handleApplyDefaultProxy(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getHttpProxy() : null); updateTcpBufferSizes(null != newDefaultNetwork @@ -7799,12 +7866,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0; mLegacyTypeTracker.add( newDefaultNetwork.networkInfo.getType(), newDefaultNetwork); - // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast - // to reflect the NetworkInfo of this new network. This broadcast has to be sent - // after the disconnect broadcasts above, but before the broadcasts sent by the - // legacy type tracker below. - // TODO : refactor this, it's too complex - notifyLockdownVpn(newDefaultNetwork); } } @@ -7862,18 +7923,6 @@ public class ConnectivityService extends IConnectivityManager.Stub sendInetConditionBroadcast(nai.networkInfo); } - private void notifyLockdownVpn(NetworkAgentInfo nai) { - synchronized (mVpns) { - if (mLockdownTracker != null) { - if (nai != null && nai.isVPN()) { - mLockdownTracker.onVpnStateChanged(nai.networkInfo); - } else { - mLockdownTracker.onNetworkInfoChanged(); - } - } - } - } - @NonNull private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) { final NetworkInfo newInfo = new NetworkInfo(info); @@ -7912,7 +7961,6 @@ public class ConnectivityService extends IConnectivityManager.Stub oldInfo = networkAgent.networkInfo; networkAgent.networkInfo = newInfo; } - notifyLockdownVpn(networkAgent); if (DBG) { log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from " diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 7905f577d1..1da4e317a0 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -7339,11 +7339,14 @@ public class ConnectivityServiceTest { when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); } - private void establishLegacyLockdownVpn() throws Exception { + private void establishLegacyLockdownVpn(Network underlying) throws Exception { + // The legacy lockdown VPN only supports userId 0, and must have an underlying network. + assertNotNull(underlying); mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY); // The legacy lockdown VPN only supports userId 0. final Set ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); mMockVpn.registerAgent(ranges); + mMockVpn.setUnderlyingNetworks(new Network[]{underlying}); mMockVpn.connect(true); } @@ -7351,6 +7354,9 @@ public class ConnectivityServiceTest { public void testLegacyLockdownVpn() throws Exception { mServiceContext.setPermission( Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); + // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback. + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); final TestNetworkCallback callback = new TestNetworkCallback(); @@ -7359,6 +7365,10 @@ public class ConnectivityServiceTest { final TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); + final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + // Pretend lockdown VPN was configured. setupLegacyLockdownVpn(); @@ -7388,6 +7398,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false /* validated */); callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); waitForIdle(); assertNull(mMockVpn.getAgent()); @@ -7399,6 +7410,8 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); waitForIdle(); assertNull(mMockVpn.getAgent()); @@ -7408,6 +7421,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.disconnect(); callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); b1.expectBroadcast(); // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten @@ -7417,6 +7431,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false /* validated */); callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); b1.expectBroadcast(); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); @@ -7439,9 +7454,10 @@ public class ConnectivityServiceTest { mMockVpn.expectStartLegacyVpnRunner(); b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); - establishLegacyLockdownVpn(); + establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork()); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); b1.expectBroadcast(); b2.expectBroadcast(); @@ -7453,9 +7469,7 @@ public class ConnectivityServiceTest { assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); - VpnTransportInfo ti = (VpnTransportInfo) vpnNc.getTransportInfo(); - assertNotNull(ti); - assertEquals(VpnManager.TYPE_VPN_LEGACY, ti.type); + assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY); // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect. final LinkProperties wifiLp = new LinkProperties(); @@ -7483,11 +7497,10 @@ public class ConnectivityServiceTest { // fact that a VPN is connected should only result in the VPN itself being unblocked, not // any other network. Bug in isUidBlockedByVpn? callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI)); callback.expectCallback(CallbackEntry.LOST, mMockVpn); - defaultCallback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI)); defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // While the VPN is reconnecting on the new network, everything is blocked. assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); @@ -7498,9 +7511,10 @@ public class ConnectivityServiceTest { // The VPN comes up again on wifi. b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); - establishLegacyLockdownVpn(); + establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork()); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); b1.expectBroadcast(); b2.expectBroadcast(); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7514,14 +7528,10 @@ public class ConnectivityServiceTest { assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); // Disconnect cell. Nothing much happens since it's not the default network. - // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any - // NetworkInfo is updated. This is probably a bug. - // TODO: consider fixing this. - b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mCellNetworkAgent.disconnect(); - b1.expectBroadcast(); callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultCallback.assertNoCallback(); + systemDefaultCallback.assertNoCallback(); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); @@ -7531,6 +7541,7 @@ public class ConnectivityServiceTest { b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); b1.expectBroadcast(); callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI)); b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED); From ee1740ddf1e4d8e7a8db730f1db1b60ab367ab08 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Mon, 9 Nov 2020 10:34:03 +0900 Subject: [PATCH 3/5] Add a skeleton VpnManagerService, and start it on boot. This adds a lot of unused code but this should make it easier to review subsequent CLs. Bug: 173331190 Test: builds, boots, "dumpsys vpnmanager" succeeds Change-Id: Ied379654a0c3ab6242d3125661fe30f322395059 --- framework/src/android/net/VpnManager.java | 6 ++++++ .../server/connectivity/NetworkNotificationManager.java | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java index 1e30283a9e..a65769cc72 100644 --- a/framework/src/android/net/VpnManager.java +++ b/framework/src/android/net/VpnManager.java @@ -76,6 +76,12 @@ public class VpnManager { @Deprecated public static final int TYPE_VPN_LEGACY = 3; + /** + * Channel for VPN notifications. + * @hide + */ + public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; + /** @hide */ @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY}) @Retention(RetentionPolicy.SOURCE) diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 3d71b0a269..901297cf34 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -79,7 +79,6 @@ public class NetworkNotificationManager { // server. public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS"; public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS"; - public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; // The context is for the current user (system server) private final Context mContext; From cd675294176c3431b32d385194e3d3b769a1b01b Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 4 Feb 2021 17:32:07 +0900 Subject: [PATCH 4/5] Move VPN code from ConnectivityService to VpnManagerService. ConnectivityService itself does not depend on mVpns or the Vpn class any more. Most of this CL is simply moving code from one class to another: - Move the AIDL calls from IConnectivityManager to IVpnManager. - Move the implementation from ConnectivityService to the new VpnManagerService. - Move the APIs from ConnectivityManager to VpnManager, but temporarily maintain some shims in ConnectivityManager for the moved calls so that existing callers do not have to be modified in this CL. - Update VpnService to call IVpnManager instead of IConnectivityManager. - Move the code that registers the VpnManager service from ConnectivityFrameworkInitializer to SystemServiceRegistry. Bug: 173331190 Test: atest HostsideVpnTests FrameworksNetTests CtsNetTestCases Change-Id: I4911e2144df721a94fa00da9edf0dc372a7091c2 --- .../net/ConnectivityFrameworkInitializer.java | 11 - .../src/android/net/ConnectivityManager.java | 129 +--- .../src/android/net/IConnectivityManager.aidl | 37 - framework/src/android/net/VpnManager.java | 123 ++- framework/src/android/net/VpnService.java | 17 +- .../android/server/ConnectivityService.java | 725 +----------------- .../net/java/android/net/VpnManagerTest.java | 22 +- .../server/ConnectivityServiceTest.java | 80 +- 8 files changed, 244 insertions(+), 900 deletions(-) diff --git a/framework/src/android/net/ConnectivityFrameworkInitializer.java b/framework/src/android/net/ConnectivityFrameworkInitializer.java index 9afa5d1311..92a792b784 100644 --- a/framework/src/android/net/ConnectivityFrameworkInitializer.java +++ b/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -49,17 +49,6 @@ public final class ConnectivityFrameworkInitializer { } ); - // TODO: move outside of the connectivity JAR - SystemServiceRegistry.registerContextAwareService( - Context.VPN_MANAGEMENT_SERVICE, - VpnManager.class, - (context) -> { - final ConnectivityManager cm = context.getSystemService( - ConnectivityManager.class); - return cm.createVpnManager(); - } - ); - SystemServiceRegistry.registerContextAwareService( Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, ConnectivityDiagnosticsManager.class, diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 8437798b7b..25b6460ddf 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -824,6 +824,7 @@ public class ConnectivityManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; + /** * A kludge to facilitate static access where a Context pointer isn't available, like in the * case of the static set/getProcessDefaultNetwork methods and from the Network class. @@ -1069,106 +1070,55 @@ public class ConnectivityManager { } /** - * Checks if a VPN app supports always-on mode. - * - * In order to support the always-on feature, an app has to - *
    - *
  • target {@link VERSION_CODES#N API 24} or above, and - *
  • not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} - * meta-data field. - *
- * - * @param userId The identifier of the user for whom the VPN app is installed. - * @param vpnPackage The canonical package name of the VPN app. - * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ + @Deprecated public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { - try { - return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage); } /** - * Configures an always-on VPN connection through a specific application. - * This connection is automatically granted and persisted after a reboot. - * - *

The designated package should declare a {@link VpnService} in its - * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, - * otherwise the call will fail. - * - * @param userId The identifier of the user to set an always-on VPN for. - * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} - * to remove an existing always-on VPN configuration. - * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. - * @param lockdownAllowlist The list of packages that are allowed to access network directly - * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so - * this method must be called when a package that should be allowed is installed or - * uninstalled. - * @return {@code true} if the package is set as always-on VPN controller; - * {@code false} otherwise. + * Calls VpnManager#setAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, boolean lockdownEnabled, @Nullable List lockdownAllowlist) { - try { - return mService.setAlwaysOnVpnPackage( - userId, vpnPackage, lockdownEnabled, lockdownAllowlist); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled, + lockdownAllowlist); } - /** - * Returns the package name of the currently set always-on VPN application. - * If there is no always-on VPN set, or the VPN is provided by the system instead - * of by an app, {@code null} will be returned. - * - * @return Package name of VPN controller responsible for always-on VPN, - * or {@code null} if none is set. + /** + * Calls VpnManager#getAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public String getAlwaysOnVpnPackageForUser(int userId) { - try { - return mService.getAlwaysOnVpnPackage(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().getAlwaysOnVpnPackageForUser(userId); } /** - * @return whether always-on VPN is in lockdown mode. - * + * Calls VpnManager#isVpnLockdownEnabled. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + */ + @Deprecated public boolean isVpnLockdownEnabled(int userId) { - try { - return mService.isVpnLockdownEnabled(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - + return getVpnManager().isVpnLockdownEnabled(userId); } /** - * @return the list of packages that are allowed to access network when always-on VPN is in - * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. - * + * Calls VpnManager#getVpnLockdownAllowlist. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + */ + @Deprecated public List getVpnLockdownWhitelist(int userId) { - try { - return mService.getVpnLockdownWhitelist(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().getVpnLockdownAllowlist(userId); } /** @@ -3219,20 +3169,13 @@ public class ConnectivityManager { } /** - * If the LockdownVpn mechanism is enabled, updates the vpn - * with a reload of its profile. - * - * @return a boolean with {@code} indicating success - * - *

This method can only be called by the system UID - * {@hide} + * Calls VpnManager#updateLockdownVpn. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide */ + @Deprecated public boolean updateLockdownVpn() { - try { - return mService.updateLockdownVpn(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().updateLockdownVpn(); } /** @@ -4596,6 +4539,8 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); + // TODO: Migrate callers to VpnManager#factoryReset. + getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4889,9 +4834,13 @@ public class ConnectivityManager { return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); } - /** @hide */ - public VpnManager createVpnManager() { - return new VpnManager(mContext, mService); + /** + * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a + * private final mVpnManager because ConnectivityManager is initialized before VpnManager. + * @hide TODO: remove. + */ + public VpnManager getVpnManager() { + return mContext.getSystemService(VpnManager.class); } /** @hide */ diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index ab134eb6d2..77aee5ec6f 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -42,9 +42,6 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import com.android.connectivity.aidl.INetworkAgent; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; /** * Interface that answers queries about, and allows changing, the @@ -122,34 +119,6 @@ interface IConnectivityManager ProxyInfo getProxyForNetwork(in Network nework); - boolean prepareVpn(String oldPackage, String newPackage, int userId); - - void setVpnPackageAuthorization(String packageName, int userId, int vpnType); - - ParcelFileDescriptor establishVpn(in VpnConfig config); - - boolean provisionVpnProfile(in VpnProfile profile, String packageName); - - void deleteVpnProfile(String packageName); - - void startVpnProfile(String packageName); - - void stopVpnProfile(String packageName); - - VpnConfig getVpnConfig(int userId); - - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - void startLegacyVpn(in VpnProfile profile); - - LegacyVpnInfo getLegacyVpnInfo(int userId); - - boolean updateLockdownVpn(); - boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); - boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, - in List lockdownWhitelist); - String getAlwaysOnVpnPackage(int userId); - boolean isVpnLockdownEnabled(int userId); - List getVpnLockdownWhitelist(int userId); void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); void setLegacyLockdownVpnEnabled(boolean enabled); @@ -200,10 +169,6 @@ interface IConnectivityManager int getRestoreDefaultNetworkDelay(int networkType); - boolean addVpnAddress(String address, int prefixLength); - boolean removeVpnAddress(String address, int prefixLength); - boolean setUnderlyingNetworksForVpn(in Network[] networks); - void factoryReset(); void startNattKeepalive(in Network network, int intervalSeconds, @@ -223,8 +188,6 @@ interface IConnectivityManager byte[] getNetworkWatchlistConfigHash(); int getConnectionOwnerUid(in ConnectionInfo connectionInfo); - boolean isCallerCurrentAlwaysOnVpnApp(); - boolean isCallerCurrentAlwaysOnVpnLockdownApp(); void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, in NetworkRequest request, String callingPackageName); diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java index a65769cc72..f472ed4381 100644 --- a/framework/src/android/net/VpnManager.java +++ b/framework/src/android/net/VpnManager.java @@ -21,6 +21,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.Activity; import android.content.ComponentName; @@ -37,6 +38,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.GeneralSecurityException; +import java.util.List; /** * This class provides an interface for apps to manage platform VPN profiles @@ -88,7 +90,7 @@ public class VpnManager { public @interface VpnType {} @NonNull private final Context mContext; - @NonNull private final IConnectivityManager mService; + @NonNull private final IVpnManager mService; private static Intent getIntentForConfirmation() { final Intent intent = new Intent(); @@ -107,9 +109,9 @@ public class VpnManager { * * @hide */ - public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) { + public VpnManager(@NonNull Context ctx, @NonNull IVpnManager service) { mContext = checkNotNull(ctx, "missing Context"); - mService = checkNotNull(service, "missing IConnectivityManager"); + mService = checkNotNull(service, "missing IVpnManager"); } /** @@ -200,6 +202,19 @@ public class VpnManager { } } + /** + * Resets all VPN settings back to factory defaults. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void factoryReset() { + try { + mService.factoryReset(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Prepare for a VPN application. * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, @@ -245,6 +260,108 @@ public class VpnManager { } } + /** + * Checks if a VPN app supports always-on mode. + * + * In order to support the always-on feature, an app has to + *

    + *
  • target {@link VERSION_CODES#N API 24} or above, and + *
  • not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} + * meta-data field. + *
+ * + * @param userId The identifier of the user for whom the VPN app is installed. + * @param vpnPackage The canonical package name of the VPN app. + * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * @hide + */ + public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { + try { + return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Configures an always-on VPN connection through a specific application. + * This connection is automatically granted and persisted after a reboot. + * + *

The designated package should declare a {@link VpnService} in its + * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, + * otherwise the call will fail. + * + * @param userId The identifier of the user to set an always-on VPN for. + * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration. + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. + * @param lockdownAllowlist The list of packages that are allowed to access network directly + * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so + * this method must be called when a package that should be allowed is installed or + * uninstalled. + * @return {@code true} if the package is set as always-on VPN controller; + * {@code false} otherwise. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List lockdownAllowlist) { + try { + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, lockdownAllowlist); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the package name of the currently set always-on VPN application. + * If there is no always-on VPN set, or the VPN is provided by the system instead + * of by an app, {@code null} will be returned. + * + * @return Package name of VPN controller responsible for always-on VPN, + * or {@code null} if none is set. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public String getAlwaysOnVpnPackageForUser(int userId) { + try { + return mService.getAlwaysOnVpnPackage(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return whether always-on VPN is in lockdown mode. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean isVpnLockdownEnabled(int userId) { + try { + return mService.isVpnLockdownEnabled(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the list of packages that are allowed to access network when always-on VPN is in + * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public List getVpnLockdownAllowlist(int userId) { + try { + return mService.getVpnLockdownAllowlist(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Return the legacy VPN information for the specified user ID. * @hide diff --git a/framework/src/android/net/VpnService.java b/framework/src/android/net/VpnService.java index 8e90a119fe..e43b0b6fa6 100644 --- a/framework/src/android/net/VpnService.java +++ b/framework/src/android/net/VpnService.java @@ -170,12 +170,11 @@ public class VpnService extends Service { "android.net.VpnService.SUPPORTS_ALWAYS_ON"; /** - * Use IConnectivityManager since those methods are hidden and not - * available in ConnectivityManager. + * Use IVpnManager since those methods are hidden and not available in VpnManager. */ - private static IConnectivityManager getService() { - return IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + private static IVpnManager getService() { + return IVpnManager.Stub.asInterface( + ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE)); } /** @@ -226,15 +225,15 @@ public class VpnService extends Service { @SystemApi @RequiresPermission(android.Manifest.permission.CONTROL_VPN) public static void prepareAndAuthorize(Context context) { - IConnectivityManager cm = getService(); + IVpnManager vm = getService(); String packageName = context.getPackageName(); try { // Only prepare if we're not already prepared. int userId = context.getUserId(); - if (!cm.prepareVpn(packageName, null, userId)) { - cm.prepareVpn(null, packageName, userId); + if (!vm.prepareVpn(packageName, null, userId)) { + vm.prepareVpn(null, packageName, userId); } - cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); + vm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); } catch (RemoteException e) { // ignore } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 84aaca0ae2..a3a17b3504 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -16,7 +16,6 @@ package com.android.server; -import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; @@ -138,7 +137,6 @@ import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; -import android.net.VpnService; import android.net.VpnTransportInfo; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; @@ -170,8 +168,6 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.security.Credentials; -import android.security.KeyStore; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; @@ -187,9 +183,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; import com.android.internal.util.ArrayUtils; import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; @@ -214,9 +207,7 @@ import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; -import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; -import com.android.server.net.LockdownVpnTracker; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.utils.PriorityDump; @@ -309,18 +300,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private final PerUidCounter mNetworkRequestCounter; - private KeyStore mKeyStore; - - @VisibleForTesting - @GuardedBy("mVpns") - protected final SparseArray mVpns = new SparseArray<>(); - - // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by - // a direct call to LockdownVpnTracker.isEnabled(). - @GuardedBy("mVpns") private volatile boolean mLockdownEnabled; - @GuardedBy("mVpns") - private LockdownVpnTracker mLockdownTracker; /** * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal @@ -997,13 +977,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return NetworkStackClient.getInstance(); } - /** - * Get a reference to the system keystore. - */ - public KeyStore getKeyStore() { - return KeyStore.getInstance(); - } - /** * @see ProxyTracker */ @@ -1113,7 +1086,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler); mNetd = netd; - mKeyStore = mDeps.getKeyStore(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mLocationPermissionChecker = new LocationPermissionChecker(mContext); @@ -1202,43 +1174,15 @@ public class ConnectivityService extends IConnectivityManager.Stub mPermissionMonitor = new PermissionMonitor(mContext, mNetd); - // Set up the listener for user state for creating user VPNs. + // Listen for user add/removes to inform PermissionMonitor. // Should run on mHandler to avoid any races. IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_STARTED); - intentFilter.addAction(Intent.ACTION_USER_STOPPED); intentFilter.addAction(Intent.ACTION_USER_ADDED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); - intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver( - mUserPresentReceiver, - new IntentFilter(Intent.ACTION_USER_PRESENT), - null /* broadcastPermission */, - null /* scheduler */); - - // Listen to package add and removal events for all users. - intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addDataScheme("package"); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - - // Listen to lockdown VPN reset. - intentFilter = new IntentFilter(); - intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); - mUserAllContext.registerReceiver( - mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); + mUserAllContext.registerReceiver(mIntentReceiver, intentFilter, + null /* broadcastPermission */, mHandler); mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS); @@ -1416,9 +1360,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Network[] getVpnUnderlyingNetworks(int uid) { - synchronized (mVpns) { - if (mLockdownEnabled) return null; - } + if (mLockdownEnabled) return null; final NetworkAgentInfo nai = getVpnForUid(uid); if (nai != null) return nai.declaredUnderlyingNetworks; return null; @@ -2185,22 +2127,6 @@ public class ConnectivityService extends IConnectivityManager.Stub isBackgroundRestricted); } - /** - * Require that the caller is either in the same user or has appropriate permission to interact - * across users. - * - * @param userId Target user for whatever operation the current IPC is supposed to perform. - */ - private void enforceCrossUserPermission(int userId) { - if (userId == UserHandle.getCallingUserId()) { - // Not a cross-user call. - return; - } - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "ConnectivityService"); - } - private boolean checkAnyPermissionOf(String... permissions) { for (String permission : permissions) { if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { @@ -2281,12 +2207,6 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid); } - private void enforceControlAlwaysOnVpnPermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, - "ConnectivityService"); - } - private void enforceNetworkStackOrSettingsPermission() { enforceAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, @@ -2452,10 +2372,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait - // for user to unlock device too. - updateLockdownVpn(); - // Create network requests for always-on networks. mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS)); } @@ -4739,183 +4655,6 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.e(TAG, s, t); } - /** - * Prepare for a VPN application. - * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, - * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param oldPackage Package name of the application which currently controls VPN, which will - * be replaced. If there is no such application, this should should either be - * {@code null} or {@link VpnConfig.LEGACY_VPN}. - * @param newPackage Package name of the application which should gain control of VPN, or - * {@code null} to disable. - * @param userId User for whom to prepare the new VPN. - * - * @hide - */ - @Override - public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, - int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - throwIfLockdownEnabled(); - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE); - } else { - return false; - } - } - } - - /** - * Set whether the VPN package has the ability to launch VPNs without user intervention. This - * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn} - * class. If the caller is not {@code userId}, {@link - * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param packageName The package for which authorization state should change. - * @param userId User for whom {@code packageName} is installed. - * @param authorized {@code true} if this app should be able to start a VPN connection without - * explicit user approval, {@code false} if not. - * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN - * permissions should be granted. When unauthorizing an app, {@link - * VpnManager.TYPE_VPN_NONE} should be used. - * @hide - */ - @Override - public void setVpnPackageAuthorization( - String packageName, int userId, @VpnManager.VpnType int vpnType) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - vpn.setPackageAuthorization(packageName, vpnType); - } - } - } - - /** - * Configure a TUN interface and return its file descriptor. Parameters - * are encoded and opaque to this class. This method is used by VpnBuilder - * and not available in ConnectivityManager. Permissions are checked in - * Vpn class. - * @hide - */ - @Override - public ParcelFileDescriptor establishVpn(VpnConfig config) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).establish(config); - } - } - - /** - * Stores the given VPN profile based on the provisioning package name. - * - *

If there is already a VPN profile stored for the provisioning package, this call will - * overwrite the profile. - * - *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @return {@code true} if user consent has already been granted, {@code false} otherwise. - * @hide - */ - @Override - public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore); - } - } - - /** - * Deletes the stored VPN profile for the provisioning package - * - *

If there are no profiles for the given package, this method will silently succeed. - * - *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @hide - */ - @Override - public void deleteVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - mVpns.get(user).deleteVpnProfile(packageName, mKeyStore); - } - } - - /** - * Starts the VPN based on the stored profile for the given package - * - *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @throws IllegalArgumentException if no profile was found for the given package name. - * @hide - */ - @Override - public void startVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - mVpns.get(user).startVpnProfile(packageName, mKeyStore); - } - } - - /** - * Stops the Platform VPN if the provided package is running one. - * - *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @hide - */ - @Override - public void stopVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - mVpns.get(user).stopVpnProfile(packageName); - } - } - - /** - * Start legacy VPN, controlling native daemons as needed. Creates a - * secondary thread to perform connection work, returning quickly. - */ - @Override - public void startLegacyVpn(VpnProfile profile) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final LinkProperties egress = getActiveLinkProperties(); - if (egress == null) { - throw new IllegalStateException("Missing active network connection"); - } - synchronized (mVpns) { - throwIfLockdownEnabled(); - mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress); - } - } - - /** - * Return the information of the ongoing legacy VPN. This method is used - * by VpnSettings and not available in ConnectivityManager. Permissions - * are checked in Vpn class. - */ - @Override - public LegacyVpnInfo getLegacyVpnInfo(int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - return mVpns.get(userId).getLegacyVpnInfo(); - } - } - /** * Return the information of all ongoing VPNs. * @@ -4925,10 +4664,8 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private UnderlyingNetworkInfo[] getAllVpnInfo() { ensureRunningOnConnectivityServiceThread(); - synchronized (mVpns) { - if (mLockdownEnabled) { - return new UnderlyingNetworkInfo[0]; - } + if (mLockdownEnabled) { + return new UnderlyingNetworkInfo[0]; } List infoList = new ArrayList<>(); for (NetworkAgentInfo nai : mNetworkAgentInfos) { @@ -4984,25 +4721,6 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.linkProperties.getInterfaceName(), interfaces); } - /** - * Returns the information of the ongoing VPN for {@code userId}. This method is used by - * VpnDialogs and not available in ConnectivityManager. - * Permissions are checked in Vpn class. - * @hide - */ - @Override - public VpnConfig getVpnConfig(int userId) { - enforceCrossUserPermission(userId); - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.getVpnConfig(); - } else { - return null; - } - } - } - // TODO This needs to be the default network that applies to the NAI. private Network[] underlyingNetworksOrDefault(final int ownerUid, Network[] underlyingNetworks) { @@ -5096,11 +4814,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.post(() -> mLockdownEnabled = enabled); } - // TODO: remove when the VPN code moves out. - private boolean isLockdownVpnEnabled() { - return mKeyStore.contains(Credentials.LOCKDOWN_VPN); - } - private boolean isLegacyLockdownNai(NetworkAgentInfo nai) { return mLockdownEnabled && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY @@ -5143,193 +4856,6 @@ public class ConnectivityService extends IConnectivityManager.Stub : DetailedState.CONNECTED; } - @Override - public boolean updateLockdownVpn() { - // Allow the system UID for the system server and for Settings. - // Also, for unit tests, allow the process that ConnectivityService is running in. - if (mDeps.getCallingUid() != Process.SYSTEM_UID - && Binder.getCallingPid() != Process.myPid()) { - logw("Lockdown VPN only available to system process or AID_SYSTEM"); - return false; - } - - synchronized (mVpns) { - // Tear down existing lockdown if profile was removed - if (!isLockdownVpnEnabled()) { - setLockdownTracker(null); - return true; - } - - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); - if (profileTag == null) { - loge("Lockdown VPN configured but cannot be read from keystore"); - return false; - } - String profileName = new String(profileTag); - final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); - if (profile == null) { - loge("Lockdown VPN configured invalid profile " + profileName); - setLockdownTracker(null); - return true; - } - int user = UserHandle.getUserId(mDeps.getCallingUid()); - Vpn vpn = mVpns.get(user); - if (vpn == null) { - logw("VPN for user " + user + " not ready yet. Skipping lockdown"); - return false; - } - setLockdownTracker( - new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile)); - } - - return true; - } - - /** - * Internally set new {@link LockdownVpnTracker}, shutting down any existing - * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. - */ - @GuardedBy("mVpns") - private void setLockdownTracker(LockdownVpnTracker tracker) { - // Shutdown any existing tracker - final LockdownVpnTracker existing = mLockdownTracker; - // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the - // necessary onBlockedStatusChanged callbacks. - mLockdownTracker = null; - if (existing != null) { - existing.shutdown(); - } - - if (tracker != null) { - mLockdownTracker = tracker; - mLockdownTracker.init(); - } - } - - /** - * Throws if there is any currently running, always-on Legacy VPN. - * - *

The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is - * running across the entire system. Tracking for app-based VPNs is done on a per-user, - * per-package basis in Vpn.java - */ - @GuardedBy("mVpns") - private void throwIfLockdownEnabled() { - if (mLockdownEnabled) { - throw new IllegalStateException("Unavailable in lockdown mode"); - } - } - - /** - * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform - * some setup and then call {@code establish()} to connect. - * - * @return {@code true} if the service was started, the service was already connected, or there - * was no always-on VPN to start. {@code false} otherwise. - */ - private boolean startAlwaysOnVpn(int userId) { - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - // Shouldn't happen as all code paths that point here should have checked the Vpn - // exists already. - Log.wtf(TAG, "User " + userId + " has no Vpn configuration"); - return false; - } - - return vpn.startAlwaysOnVpn(mKeyStore); - } - } - - @Override - public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { - enforceSettingsPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); - } - } - - @Override - public boolean setAlwaysOnVpnPackage( - int userId, String packageName, boolean lockdown, List lockdownWhitelist) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - // Can't set always-on VPN if legacy VPN is already in lockdown mode. - if (isLockdownVpnEnabled()) { - return false; - } - - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) { - return false; - } - if (!startAlwaysOnVpn(userId)) { - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - return false; - } - } - return true; - } - - @Override - public String getAlwaysOnVpnPackage(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getAlwaysOnPackage(); - } - } - - @Override - public boolean isVpnLockdownEnabled(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.getLockdown(); - } - } - - @Override - public List getVpnLockdownWhitelist(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getLockdownAllowlist(); - } - } - @Override public void setProvisioningNotificationVisible(boolean visible, int networkType, String action) { @@ -5362,165 +4888,33 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void onUserStarted(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn != null) { - loge("Starting user already has a VPN"); - return; - } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); - mVpns.put(userId, userVpn); - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } - } - } - - private void onUserStopped(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn == null) { - loge("Stopped user has no VPN"); - return; - } - userVpn.onUserStopped(); - mVpns.delete(userId); - } - } - private void onUserAdded(int userId) { mPermissionMonitor.onUserAdded(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserAdded(userId); - } - } } private void onUserRemoved(int userId) { mPermissionMonitor.onUserRemoved(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserRemoved(userId); - } - } - } - - private void onPackageReplaced(String packageName, int uid) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); - return; - } - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { - log("Restarting always-on VPN package " + packageName + " for user " - + userId); - vpn.startAlwaysOnVpn(mKeyStore); - } - } - } - - private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); - return; - } - - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { - log("Removing always-on VPN package " + packageName + " for user " - + userId); - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - } - } - } - - private void onUserUnlocked(int userId) { - synchronized (mVpns) { - // User present may be sent because of an unlock, which might mean an unlocked keystore. - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } else { - startAlwaysOnVpn(userId); - } - } - } - - private void onVpnLockdownReset() { - synchronized (mVpns) { - if (mLockdownTracker != null) mLockdownTracker.reset(); - } } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - ensureRunningOnConnectivityServiceThread(); final String action = intent.getAction(); final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); - final Uri packageData = intent.getData(); - final String packageName = - packageData != null ? packageData.getSchemeSpecificPart() : null; - - if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { - onVpnLockdownReset(); - } // UserId should be filled for below intents, check the existence. if (userId == UserHandle.USER_NULL) return; - if (Intent.ACTION_USER_STARTED.equals(action)) { - onUserStarted(userId); - } else if (Intent.ACTION_USER_STOPPED.equals(action)) { - onUserStopped(userId); - } else if (Intent.ACTION_USER_ADDED.equals(action)) { + if (Intent.ACTION_USER_ADDED.equals(action)) { onUserAdded(userId); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { onUserRemoved(userId); - } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { - onUserUnlocked(userId); - } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { - onPackageReplaced(packageName, uid); - } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - final boolean isReplacing = intent.getBooleanExtra( - Intent.EXTRA_REPLACING, false); - onPackageRemoved(packageName, uid, isReplacing); - } else { + } else { Log.wtf(TAG, "received unexpected intent: " + action); } } }; - private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Try creating lockdown tracker, since user present usually means - // unlocked keystore. - updateLockdownVpn(); - // Use the same context that registered receiver before to unregister it. Because use - // different context to unregister receiver will cause exception. - context.unregisterReceiver(this); - } - }; - private final HashMap mNetworkProviderInfos = new HashMap<>(); private final HashMap mNetworkRequests = new HashMap<>(); @@ -8260,34 +7654,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - @Override - public boolean addVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).addAddress(address, prefixLength); - } - } - - @Override - public boolean removeVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).removeAddress(address, prefixLength); - } - } - - @Override - public boolean setUnderlyingNetworksForVpn(Network[] networks) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final boolean success; - synchronized (mVpns) { - success = mVpns.get(user).setUnderlyingNetworks(networks); - } - return success; - } - @Override public String getCaptivePortalServerUrl() { enforceNetworkStackOrSettingsPermission(); @@ -8367,8 +7733,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - final int userId = UserHandle.getCallingUserId(); - final long token = Binder.clearCallingIdentity(); try { final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext); @@ -8380,44 +7744,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // Turn airplane mode off setAirplaneMode(false); - if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { - // Remove always-on package - synchronized (mVpns) { - final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); - if (alwaysOnPackage != null) { - setAlwaysOnVpnPackage(userId, null, false, null); - setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); - } - - // Turn Always-on VPN off - if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { - final long ident = Binder.clearCallingIdentity(); - try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); - mLockdownEnabled = false; - setLockdownTracker(null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - // Turn VPN off - VpnConfig vpnConfig = getVpnConfig(userId); - if (vpnConfig != null) { - if (vpnConfig.legacy) { - prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); - } else { - // Prevent this app (packagename = vpnConfig.user) from initiating - // VPN connections in the future without user intervention. - setVpnPackageAuthorization( - vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); - - prepareVpn(null, VpnConfig.LEGACY_VPN, userId); - } - } - } - } - // restore private DNS settings to default mode (opportunistic) if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) { Settings.Global.putString(mContext.getContentResolver(), @@ -8509,25 +7835,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - @GuardedBy("mVpns") - private Vpn getVpnIfOwner() { - return getVpnIfOwner(mDeps.getCallingUid()); - } - - // TODO: stop calling into Vpn.java and get this information from data in this class. - @GuardedBy("mVpns") - private Vpn getVpnIfOwner(int uid) { - final int user = UserHandle.getUserId(uid); - - final Vpn vpn = mVpns.get(user); - if (vpn == null) { - return null; - } else { - final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo(); - return (info == null || info.ownerUid != uid) ? null : vpn; - } - } - private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) { if (vpn == null) return VpnManager.TYPE_VPN_NONE; final TransportInfo ti = vpn.networkCapabilities.getTransportInfo(); @@ -8564,22 +7871,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return uid; } - @Override - public boolean isCallerCurrentAlwaysOnVpnApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getAlwaysOn(); - } - } - - @Override - public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getLockdown(); - } - } - /** * Returns a IBinder to a TestNetworkService. Will be lazily created as needed. * diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java index 95a794235a..c548e30761 100644 --- a/tests/net/java/android/net/VpnManagerTest.java +++ b/tests/net/java/android/net/VpnManagerTest.java @@ -49,7 +49,7 @@ public class VpnManagerTest { private static final String IDENTITY_STRING = "Identity"; private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); - private IConnectivityManager mMockCs; + private IVpnManager mMockService; private VpnManager mVpnManager; private final MockContext mMockContext = new MockContext() { @@ -61,24 +61,26 @@ public class VpnManagerTest { @Before public void setUp() throws Exception { - mMockCs = mock(IConnectivityManager.class); - mVpnManager = new VpnManager(mMockContext, mMockCs); + mMockService = mock(IVpnManager.class); + mVpnManager = new VpnManager(mMockContext, mMockService); } @Test public void testProvisionVpnProfilePreconsented() throws Exception { final PlatformVpnProfile profile = getPlatformVpnProfile(); - when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(true); // Expect there to be no intent returned, as consent has already been granted. assertNull(mVpnManager.provisionVpnProfile(profile)); - verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); } @Test public void testProvisionVpnProfileNeedsConsent() throws Exception { final PlatformVpnProfile profile = getPlatformVpnProfile(); - when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(false); // Expect intent to be returned, as consent has not already been granted. final Intent intent = mVpnManager.provisionVpnProfile(profile); @@ -88,25 +90,25 @@ public class VpnManagerTest { ComponentName.unflattenFromString( "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog"); assertEquals(expectedComponentName, intent.getComponent()); - verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); } @Test public void testDeleteProvisionedVpnProfile() throws Exception { mVpnManager.deleteProvisionedVpnProfile(); - verify(mMockCs).deleteVpnProfile(eq(PKG_NAME)); + verify(mMockService).deleteVpnProfile(eq(PKG_NAME)); } @Test public void testStartProvisionedVpnProfile() throws Exception { mVpnManager.startProvisionedVpnProfile(); - verify(mMockCs).startVpnProfile(eq(PKG_NAME)); + verify(mMockService).startVpnProfile(eq(PKG_NAME)); } @Test public void testStopProvisionedVpnProfile() throws Exception { mVpnManager.stopProvisionedVpnProfile(); - verify(mMockCs).stopVpnProfile(eq(PKG_NAME)); + verify(mMockService).stopVpnProfile(eq(PKG_NAME)); } private Ikev2VpnProfile getPlatformVpnProfile() throws Exception { diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 1da4e317a0..917a359523 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -370,6 +370,7 @@ public class ConnectivityServiceTest { private MockContext mServiceContext; private HandlerThread mCsHandlerThread; + private HandlerThread mVMSHandlerThread; private ConnectivityService.Dependencies mDeps; private ConnectivityService mService; private WrappedConnectivityManager mCm; @@ -384,6 +385,7 @@ public class ConnectivityServiceTest { private TestNetIdManager mNetIdManager; private QosCallbackMockHelper mQosCallbackMockHelper; private QosCallbackTracker mQosCallbackTracker; + private VpnManagerService mVpnManagerService; // State variables required to emulate NetworkPolicyManagerService behaviour. private int mUidRules = RULE_NONE; @@ -1256,15 +1258,29 @@ public class ConnectivityServiceTest { r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new); } - private void mockVpn(int uid) { - synchronized (mService.mVpns) { - int userId = UserHandle.getUserId(uid); - mMockVpn = new MockVpn(userId); - // This has no effect unless the VPN is actually connected, because things like - // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN - // netId, and check if that network is actually connected. - mService.mVpns.put(userId, mMockVpn); - } + private VpnManagerService makeVpnManagerService() { + final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() { + public int getCallingUid() { + return mDeps.getCallingUid(); + } + + public HandlerThread makeHandlerThread() { + return mVMSHandlerThread; + } + + public KeyStore getKeyStore() { + return mKeyStore; + } + + public INetd getNetd() { + return mMockNetd; + } + + public INetworkManagementService getINetworkManagementService() { + return mNetworkManagementService; + } + }; + return new VpnManagerService(mServiceContext, deps); } private void assertVpnTransportInfo(NetworkCapabilities nc, int type) { @@ -1278,11 +1294,21 @@ public class ConnectivityServiceTest { private void processBroadcastForVpn(Intent intent) { // The BroadcastReceiver for this broadcast checks it is being run on the handler thread. - final Handler handler = new Handler(mCsHandlerThread.getLooper()); + final Handler handler = new Handler(mVMSHandlerThread.getLooper()); handler.post(() -> mServiceContext.sendBroadcast(intent)); + HandlerUtils.waitForIdle(handler, TIMEOUT_MS); waitForIdle(); } + private void mockVpn(int uid) { + synchronized (mVpnManagerService.mVpns) { + int userId = UserHandle.getUserId(uid); + mMockVpn = new MockVpn(userId); + // Every running user always has a Vpn in the mVpns array, even if no VPN is running. + mVpnManagerService.mVpns.put(userId, mMockVpn); + } + } + private void mockUidNetworkingBlocked() { doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class) .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules, @@ -1406,6 +1432,7 @@ public class ConnectivityServiceTest { initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler()); mCsHandlerThread = new HandlerThread("TestConnectivityService"); + mVMSHandlerThread = new HandlerThread("TestVpnManagerService"); mDeps = makeDependencies(); returnRealCallingUid(); mService = new ConnectivityService(mServiceContext, @@ -1428,6 +1455,8 @@ public class ConnectivityServiceTest { // getSystemService() correctly. mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService); mService.systemReadyInternal(); + mVpnManagerService = makeVpnManagerService(); + mVpnManagerService.systemReady(); mockVpn(Process.myUid()); mCm.bindProcessToNetwork(null); mQosCallbackTracker = mock(QosCallbackTracker.class); @@ -1455,7 +1484,6 @@ public class ConnectivityServiceTest { doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt()); doReturn(mBatteryStatsService).when(deps).getBatteryStatsService(); - doReturn(mKeyStore).when(deps).getKeyStore(); doAnswer(inv -> { mPolicyTracker = new WrappedMultinetworkPolicyTracker( inv.getArgument(0), inv.getArgument(1), inv.getArgument(2)); @@ -6766,8 +6794,8 @@ public class ConnectivityServiceTest { // Enable always-on VPN lockdown. The main user loses network access because no VPN is up. final ArrayList allowList = new ArrayList<>(); - mService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, true /* lockdown */, - allowList); + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, + true /* lockdown */, allowList); waitForIdle(); assertNull(mCm.getActiveNetworkForUid(uid)); // This is arguably overspecified: a UID that is not running doesn't have an active network. @@ -6797,7 +6825,8 @@ public class ConnectivityServiceTest { assertNull(mCm.getActiveNetworkForUid(uid)); assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); - mService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, + allowList); waitForIdle(); } @@ -7173,7 +7202,8 @@ public class ConnectivityServiceTest { final int uid = Process.myUid(); final int userId = UserHandle.getUserId(uid); final ArrayList allowList = new ArrayList<>(); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); waitForIdle(); UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1); @@ -7195,7 +7225,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); // Disable lockdown, expect to see the network unblocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7208,7 +7238,8 @@ public class ConnectivityServiceTest { // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked. allowList.add(TEST_PACKAGE_NAME); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); callback.assertNoCallback(); defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); @@ -7241,11 +7272,12 @@ public class ConnectivityServiceTest { // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown. // Everything should now be blocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); waitForIdle(); expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3); allowList.clear(); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); waitForIdle(); expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf); defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); @@ -7258,7 +7290,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); // Disable lockdown. Everything is unblocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7270,7 +7302,8 @@ public class ConnectivityServiceTest { // Enable and disable an always-on VPN package without lockdown. Expect no changes. reset(mMockNetd); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, + allowList); inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); callback.assertNoCallback(); defaultCallback.assertNoCallback(); @@ -7281,7 +7314,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); callback.assertNoCallback(); defaultCallback.assertNoCallback(); @@ -7293,7 +7326,8 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); // Enable lockdown and connect a VPN. The VPN is not blocked. - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); From 4777edca7c33ec7c49b2498182f08e4df876d10d Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Wed, 10 Feb 2021 11:59:07 +0900 Subject: [PATCH 5/5] Check registering system default callback needs NETWORK_SETTINGS. Also address a code review comment. Bug: 173331190 Test: test-only change Change-Id: Ia68f482af6d10af203bdbd4e14a12ae0b12bb6b5 --- .../android/server/ConnectivityService.java | 3 ++- .../server/ConnectivityServiceTest.java | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index a3a17b3504..d744d34085 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -5275,7 +5275,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // changes don't alter request matching. if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT && (!networkCapabilities.equalRequestableCapabilities(defaultNc))) { - Log.wtf(TAG, "TRACK_SYSTEM_DEFAULT capabilities don't match default request: " + throw new IllegalStateException( + "TRACK_SYSTEM_DEFAULT capabilities don't match default request: " + networkCapabilities + " vs. " + defaultNc); } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 917a359523..9ec94f0fae 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -3837,6 +3837,24 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(cellNetworkCallback); } + @Test + public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception { + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false /* validated */); + + final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + final TestNetworkCallback callback = new TestNetworkCallback(); + assertThrows(SecurityException.class, + () -> mCm.registerSystemDefaultNetworkCallback(callback, handler)); + callback.assertNoCallback(); + + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, + PERMISSION_GRANTED); + mCm.registerSystemDefaultNetworkCallback(callback, handler); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + mCm.unregisterNetworkCallback(callback); + } + private void setCaptivePortalMode(int mode) { ContentResolver cr = mServiceContext.getContentResolver(); Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);