diff options
| author | 2020-11-28 03:20:40 +0000 | |
|---|---|---|
| committer | 2020-11-28 03:20:40 +0000 | |
| commit | 17199d78a26a502287c7034f1c0ab5a72d2f4467 (patch) | |
| tree | 3344ff18916821f729936767bb6fe0321748379f | |
| parent | 1a59bf1dc226f0ef12f4f0b5497955fb494c0b5c (diff) | |
| parent | d182c40d8c06a664823ef3b6416da1cd7f7b8694 (diff) | |
Move applying underlying caps from Vpn to ConnectivityService. am: d182c40d8c
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1501815
Change-Id: I10147f9b86661243e654a16a760e183128493042
6 files changed, 157 insertions, 30 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f6b1d0627312..d047a99a834f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -6175,6 +6175,7 @@ package android.net { method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); method public final void sendNetworkScore(@IntRange(from=0, to=99) int); method public final void sendSocketKeepaliveEvent(int, int); + method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>); method public void unregister(); field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 field public static final int VALIDATION_STATUS_VALID = 1; // 0x1 diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 44ebff99f3e9..0676ad4e2322 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -40,6 +40,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.Duration; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -174,6 +175,14 @@ public abstract class NetworkAgent { public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; /** + * Sent by the NetworkAgent to ConnectivityService to pass the current + * list of underlying networks. + * obj = array of Network objects + * @hide + */ + public static final int EVENT_UNDERLYING_NETWORKS_CHANGED = BASE + 5; + + /** * Sent by ConnectivityService to the NetworkAgent to inform the agent of the * networks status - whether we could use the network or could not, due to * either a bad network configuration (no internet link) or captive portal. @@ -217,7 +226,13 @@ public abstract class NetworkAgent { * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}. * @hide */ - public static String REDIRECT_URL_KEY = "redirect URL"; + public static final String REDIRECT_URL_KEY = "redirect URL"; + + /** + * Bundle key for the underlying networks in {@code EVENT_UNDERLYING_NETWORKS_CHANGED}. + * @hide + */ + public static final String UNDERLYING_NETWORKS_KEY = "underlyingNetworks"; /** * Sent by the NetworkAgent to ConnectivityService to indicate this network was @@ -650,6 +665,33 @@ public abstract class NetworkAgent { } /** + * Must be called by the agent when the network's underlying networks change. + * + * <p>{@code networks} is one of the following: + * <ul> + * <li><strong>a non-empty array</strong>: an array of one or more {@link Network}s, in + * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular) + * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear + * first in the array.</li> + * <li><strong>an empty array</strong>: a zero-element array, meaning that the VPN has no + * underlying network connection, and thus, app traffic will not be sent or received.</li> + * <li><strong>null</strong>: (default) signifies that the VPN uses whatever is the system's + * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket} + * APIs mentioned above to send traffic over specific channels.</li> + * </ul> + * + * @param underlyingNetworks the new list of underlying networks. + * @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])} + */ + public final void setUnderlyingNetworks(@Nullable List<Network> underlyingNetworks) { + final ArrayList<Network> underlyingArray = (underlyingNetworks != null) + ? new ArrayList<>(underlyingNetworks) : null; + final Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(UNDERLYING_NETWORKS_KEY, underlyingArray); + queueOrSendMessage(EVENT_UNDERLYING_NETWORKS_CHANGED, bundle); + } + + /** * Inform ConnectivityService that this agent has now connected. * Call {@link #unregister} to disconnect. */ diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index f7de5c023b4f..e96958eb3243 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2771,6 +2771,7 @@ public class ConnectivityService extends IConnectivityManager.Stub networkCapabilities = new NetworkCapabilities(networkCapabilities); networkCapabilities.restrictCapabilitesForTestNetwork(nai.creatorUid); } + processCapabilitiesFromAgent(nai, networkCapabilities); updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities); break; } @@ -2809,6 +2810,31 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeepaliveTracker.handleEventSocketKeepalive(nai, msg); break; } + case NetworkAgent.EVENT_UNDERLYING_NETWORKS_CHANGED: { + if (!nai.supportsUnderlyingNetworks()) { + Log.wtf(TAG, "Non-virtual networks cannot have underlying networks"); + break; + } + final ArrayList<Network> underlying; + try { + underlying = ((Bundle) msg.obj).getParcelableArrayList( + NetworkAgent.UNDERLYING_NETWORKS_KEY); + } catch (NullPointerException | ClassCastException e) { + break; + } + final Network[] oldUnderlying = nai.declaredUnderlyingNetworks; + nai.declaredUnderlyingNetworks = (underlying != null) + ? underlying.toArray(new Network[0]) : null; + + if (!Arrays.equals(oldUnderlying, nai.declaredUnderlyingNetworks)) { + if (DBG) { + log(nai.toShortString() + " changed underlying networks to " + + Arrays.toString(nai.declaredUnderlyingNetworks)); + } + updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities); + notifyIfacesChangedForNetworkStats(); + } + } } } @@ -3394,7 +3420,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } mLegacyTypeTracker.remove(nai, wasDefault); if (!nai.networkCapabilities.hasTransport(TRANSPORT_VPN)) { - updateAllVpnsCapabilities(); + propagateUnderlyingNetworkCapabilities(); } rematchAllNetworksAndRequests(); mLingerMonitor.noteDisconnect(nai); @@ -4774,22 +4800,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Ask all VPN objects to recompute and update their capabilities. + * Ask all networks with underlying networks to recompute and update their capabilities. * - * When underlying networks change, VPNs may have to update capabilities to reflect things - * like the metered bit, their transports, and so on. This asks the VPN objects to update - * their capabilities, and as this will cause them to send messages to the ConnectivityService - * handler thread through their agent, this is asynchronous. When the capabilities objects - * are computed they will be up-to-date as they are computed synchronously from here and - * this is running on the ConnectivityService thread. + * When underlying networks change, such networks may have to update capabilities to reflect + * things like the metered bit, their transports, and so on. The capabilities are calculated + * immediately. This method runs on the ConnectivityService thread. */ - private void updateAllVpnsCapabilities() { - Network defaultNetwork = getNetwork(getDefaultNetwork()); - synchronized (mVpns) { - for (int i = 0; i < mVpns.size(); i++) { - final Vpn vpn = mVpns.valueAt(i); - NetworkCapabilities nc = vpn.updateCapabilities(defaultNetwork); - updateVpnCapabilities(vpn, nc); + private void propagateUnderlyingNetworkCapabilities() { + ensureRunningOnConnectivityServiceThread(); + for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + if (nai.supportsUnderlyingNetworks()) { + updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities); } } } @@ -5979,6 +6000,7 @@ public class ConnectivityService extends IConnectivityManager.Stub this, mNetd, mDnsResolver, mNMS, providerId, Binder.getCallingUid()); // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says. + processCapabilitiesFromAgent(nai, nc); nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc)); processLinkPropertiesFromAgent(nai, nai.linkProperties); @@ -6019,6 +6041,12 @@ public class ConnectivityService extends IConnectivityManager.Stub updateUids(nai, null, nai.networkCapabilities); } + /** + * Called when receiving LinkProperties directly from a NetworkAgent. + * Stores into |nai| any data coming from the agent that might also be written to the network's + * LinkProperties by ConnectivityService itself. This ensures that the data provided by the + * agent is not lost when updateLinkProperties is called. + */ private void processLinkPropertiesFromAgent(NetworkAgentInfo nai, LinkProperties lp) { lp.ensureDirectlyConnectedRoutes(); nai.clatd.setNat64PrefixFromRa(lp.getNat64Prefix()); @@ -6315,6 +6343,30 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** + * Called when receiving NetworkCapabilities directly from a NetworkAgent. + * Stores into |nai| any data coming from the agent that might also be written to the network's + * NetworkCapabilities by ConnectivityService itself. This ensures that the data provided by the + * agent is not lost when updateCapabilities is called. + */ + private void processCapabilitiesFromAgent(NetworkAgentInfo nai, NetworkCapabilities nc) { + nai.declaredMetered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED); + } + + /** Propagates to |nc| the capabilities declared by the underlying networks of |nai|. */ + private void mixInUnderlyingCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) { + Network[] underlyingNetworks = nai.declaredUnderlyingNetworks; + Network defaultNetwork = getNetwork(getDefaultNetwork()); + if (underlyingNetworks == null && defaultNetwork != null) { + // null underlying networks means to track the default. + underlyingNetworks = new Network[] { defaultNetwork }; + } + + // TODO(b/124469351): Get capabilities directly from ConnectivityService instead. + final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); + Vpn.applyUnderlyingCapabilities(cm, underlyingNetworks, nc, nai.declaredMetered); + } + + /** * Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal, * and foreground status). @@ -6367,6 +6419,10 @@ public class ConnectivityService extends IConnectivityManager.Stub newNc.addCapability(NET_CAPABILITY_NOT_ROAMING); } + if (nai.supportsUnderlyingNetworks()) { + mixInUnderlyingCapabilities(nai, newNc); + } + return newNc; } @@ -6446,7 +6502,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!newNc.hasTransport(TRANSPORT_VPN)) { // Tell VPNs about updated capabilities, since they may need to // bubble those changes through. - updateAllVpnsCapabilities(); + propagateUnderlyingNetworkCapabilities(); } if (!newNc.equalsTransportTypes(prevNc)) { @@ -6766,7 +6822,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ? newNetwork.linkProperties.getTcpBufferSizes() : null); notifyIfacesChangedForNetworkStats(); // Fix up the NetworkCapabilities of any VPNs that don't specify underlying networks. - updateAllVpnsCapabilities(); + propagateUnderlyingNetworkCapabilities(); } private void processListenRequests(@NonNull final NetworkAgentInfo nai) { @@ -7228,7 +7284,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // onCapabilitiesUpdated being sent in updateAllVpnCapabilities below as // the VPN would switch from its default, blank capabilities to those // that reflect the capabilities of its underlying networks. - updateAllVpnsCapabilities(); + propagateUnderlyingNetworkCapabilities(); } networkAgent.created = true; } @@ -7270,8 +7326,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // doing. updateSignalStrengthThresholds(networkAgent, "CONNECT", null); - if (networkAgent.isVPN()) { - updateAllVpnsCapabilities(); + if (networkAgent.supportsUnderlyingNetworks()) { + propagateUnderlyingNetworkCapabilities(); } // Consider network even though it is not yet validated. @@ -7528,13 +7584,6 @@ public class ConnectivityService extends IConnectivityManager.Stub throwIfLockdownEnabled(); success = mVpns.get(user).setUnderlyingNetworks(networks); } - if (success) { - mHandler.post(() -> { - // Update VPN's capabilities based on updated underlying network set. - updateAllVpnsCapabilities(); - notifyIfacesChangedForNetworkStats(); - }); - } return success; } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index a9f62d91592d..3270dd55218c 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -132,6 +132,16 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // TODO: make this private with a getter. public NetworkCapabilities networkCapabilities; public final NetworkAgentConfig networkAgentConfig; + + // Underlying networks declared by the agent. Only set if supportsUnderlyingNetworks is true. + // The networks in this list might be declared by a VPN app using setUnderlyingNetworks and are + // not guaranteed to be current or correct, or even to exist. + public @Nullable Network[] declaredUnderlyingNetworks; + + // Whether this network is always metered even if its underlying networks are unmetered. + // Only relevant if #supportsUnderlyingNetworks is true. + public boolean declaredMetered; + // Indicates if netd has been told to create this Network. From this point on the appropriate // routing rules are setup and routes are added so packets can begin flowing over the Network. // This is a sticky bit; once set it is never cleared. @@ -474,10 +484,16 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { networkCapabilities); } + /** Whether this network is a VPN. */ public boolean isVPN() { return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); } + /** Whether this network might have underlying networks. Currently only true for VPNs. */ + public boolean supportsUnderlyingNetworks() { + return isVPN(); + } + private int getCurrentScore(boolean pretendValidated) { // TODO: We may want to refactor this into a NetworkScore class that takes a base score from // the NetworkAgent and signals from the NetworkAgent and uses those signals to modify the diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index ff1a9c0ab35f..4f5c13db1231 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -112,7 +112,6 @@ import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnInfo; import com.android.internal.net.VpnProfile; import com.android.internal.util.ArrayUtils; -import com.android.server.ConnectivityService; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.net.BaseNetworkObserver; @@ -1262,6 +1261,15 @@ public class Vpn { mNetworkCapabilities.setAdministratorUids(new int[] {mOwnerUID}); mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId, mConfig.allowedApplications, mConfig.disallowedApplications)); + + // Only apps targeting Q and above can explicitly declare themselves as metered. + // These VPNs are assumed metered unless they state otherwise. + if (mIsPackageTargetingAtLeastQ && mConfig.isMetered) { + mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_METERED); + } else { + mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); + } + final long token = Binder.clearCallingIdentity(); try { mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */, @@ -1276,6 +1284,8 @@ public class Vpn { } finally { Binder.restoreCallingIdentity(token); } + mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null) + ? Arrays.asList(mConfig.underlyingNetworks) : null); mNetworkInfo.setIsAvailable(true); updateState(DetailedState.CONNECTED, "agentConnect"); } @@ -1857,6 +1867,8 @@ public class Vpn { } } } + mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null) + ? Arrays.asList(mConfig.underlyingNetworks) : null); return true; } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 99f1985ff691..561c6bab9c6d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1084,7 +1084,7 @@ public class ConnectivityServiceTest { throws Exception { if (mAgentRegistered) throw new IllegalStateException("already registered"); setUids(uids); - mConfig.isMetered = isAlwaysMetered; + if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); mInterface = VPN_IFNAME; mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp, mNetworkCapabilities); @@ -5053,6 +5053,13 @@ public class ConnectivityServiceTest { waitForIdle(); expectForceUpdateIfaces(wifiAndVpn, null); reset(mStatsService); + + // Passing in null again means follow the default network again. + mService.setUnderlyingNetworksForVpn(null); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{WIFI_IFNAME}); + reset(mStatsService); } @Test |