diff options
| author | 2014-11-11 16:09:21 -0800 | |
|---|---|---|
| committer | 2014-11-21 09:26:18 -0800 | |
| commit | c2c0beab79a907f63e109eefe2a5aabcf2e3fd8f (patch) | |
| tree | a0a879c50b307f171dd2b0dc3a5abcb2be4afbc1 | |
| parent | 21b5ee3f0e39be4a79bcfb2b79b0529f75f5cb58 (diff) | |
Allow VPNs to specify their underlying networks.
These are used when responding to getActiveNetworkInfo() (and cousins)
when an app is subject to the VPN.
Bug: 17460017
Change-Id: Ief7a840c760777a41d3358aa6b8e4cdd99c29f24
| -rw-r--r-- | api/current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/net/IConnectivityManager.aidl | 1 | ||||
| -rw-r--r-- | core/java/android/net/VpnService.java | 55 | ||||
| -rw-r--r-- | core/java/com/android/internal/net/VpnConfig.java | 4 | ||||
| -rw-r--r-- | services/core/java/com/android/server/ConnectivityService.java | 32 | ||||
| -rw-r--r-- | services/core/java/com/android/server/connectivity/Vpn.java | 63 |
6 files changed, 146 insertions, 11 deletions
diff --git a/api/current.txt b/api/current.txt index d25ae966d907..b5c876b6d179 100644 --- a/api/current.txt +++ b/api/current.txt @@ -17443,6 +17443,7 @@ package android.net { method public boolean protect(int); method public boolean protect(java.net.Socket); method public boolean protect(java.net.DatagramSocket); + method public boolean setUnderlyingNetworks(android.net.Network[]); field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService"; } @@ -17464,6 +17465,7 @@ package android.net { method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); method public android.net.VpnService.Builder setMtu(int); method public android.net.VpnService.Builder setSession(java.lang.String); + method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]); } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index a7bbc53366f7..adc16f1a5c48 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -170,4 +170,5 @@ interface IConnectivityManager boolean addVpnAddress(String address, int prefixLength); boolean removeVpnAddress(String address, int prefixLength); + boolean setUnderlyingNetworksForVpn(in Network[] networks); } diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index d469487a7a0f..ad549128ea29 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.net.Network; import android.net.NetworkUtils; import android.os.Binder; import android.os.IBinder; @@ -288,6 +289,46 @@ public class VpnService extends Service { } /** + * Sets the underlying networks used by the VPN for its upstream connections. + * + * Used by the system to know the actual networks that carry traffic for apps affected by this + * VPN in order to present this information to the user (e.g., via status bar icons). + * + * This method only needs to be called if the VPN has explicitly bound its underlying + * communications channels — such as the socket(s) passed to {@link #protect(int)} — + * to a {@code Network} using APIs such as {@link Network#bindSocket} or {@link + * Network#bindDatagramSocket}. The VPN should call this method every time the set of {@code + * Network}s it is using changes. + * + * {@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. + * </ul> + * + * This call will succeed only if the VPN is currently established. For setting this value when + * the VPN has not yet been established, see {@link Builder#setUnderlyingNetworks}. + * + * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. + * + * @return {@code true} on success. + */ + public boolean setUnderlyingNetworks(Network[] networks) { + try { + return getService().setUnderlyingNetworksForVpn(networks); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** * Return the communication interface to the service. This method returns * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE} * action. Applications overriding this method must identify the intent @@ -663,6 +704,20 @@ public class VpnService extends Service { } /** + * Sets the underlying networks used by the VPN for its upstream connections. + * + * @see VpnService#setUnderlyingNetworks + * + * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + public Builder setUnderlyingNetworks(Network[] networks) { + mConfig.underlyingNetworks = networks != null ? networks.clone() : null; + return this; + } + + /** * Create a VPN interface using the parameters supplied to this * builder. The interface works on IP packets, and a file descriptor * is returned for the application to access them. Each read diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 3d016be98209..c5d9db486f98 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -25,6 +25,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.net.LinkAddress; +import android.net.Network; import android.net.RouteInfo; import android.os.Parcel; import android.os.Parcelable; @@ -99,6 +100,7 @@ public class VpnConfig implements Parcelable { public boolean allowBypass; public boolean allowIPv4; public boolean allowIPv6; + public Network[] underlyingNetworks; public void updateAllowedFamilies(InetAddress address) { if (address instanceof Inet4Address) { @@ -162,6 +164,7 @@ public class VpnConfig implements Parcelable { out.writeInt(allowBypass ? 1 : 0); out.writeInt(allowIPv4 ? 1 : 0); out.writeInt(allowIPv6 ? 1 : 0); + out.writeTypedArray(underlyingNetworks, flags); } public static final Parcelable.Creator<VpnConfig> CREATOR = @@ -186,6 +189,7 @@ public class VpnConfig implements Parcelable { config.allowBypass = in.readInt() != 0; config.allowIPv4 = in.readInt() != 0; config.allowIPv6 = in.readInt() != 0; + config.underlyingNetworks = in.createTypedArray(Network.CREATOR); return config; } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ec0e15c2d5c6..b1e932d38861 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -865,6 +865,29 @@ public class ConnectivityService extends IConnectivityManager.Stub Network network = null; NetworkAgentInfo nai = mNetworkForRequestId.get(mDefaultRequest.requestId); + + if (!mLockdownEnabled) { + int user = UserHandle.getUserId(uid); + synchronized (mVpns) { + Vpn vpn = mVpns.get(user); + if (vpn != null && vpn.appliesToUid(uid)) { + // getUnderlyingNetworks() returns: + // null => the VPN didn't specify anything, so we use the default. + // empty array => the VPN explicitly said "no default network". + // non-empty array => the VPN specified one or more default networks; we use the + // first one. + Network[] networks = vpn.getUnderlyingNetworks(); + if (networks != null) { + if (networks.length > 0) { + nai = getNetworkAgentInfoForNetwork(networks[0]); + } else { + nai = null; + } + } + } + } + } + if (nai != null) { synchronized (nai) { info = new NetworkInfo(nai.networkInfo); @@ -4376,4 +4399,13 @@ public class ConnectivityService extends IConnectivityManager.Stub return mVpns.get(user).removeAddress(address, prefixLength); } } + + @Override + public boolean setUnderlyingNetworksForVpn(Network[] networks) { + throwIfLockdownEnabled(); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized (mVpns) { + return mVpns.get(user).setUnderlyingNetworks(networks); + } + } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 03c05ecd464d..6da186f04320 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -46,6 +46,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.NetworkInfo; @@ -580,7 +581,13 @@ public class Vpn { } private boolean isRunningLocked() { - return mVpnUsers != null; + return mNetworkAgent != null && mInterface != null; + } + + // Returns true if the VPN has been established and the calling UID is its owner. Used to check + // that a call to mutate VPN state is admissible. + private boolean isCallerEstablishedOwnerLocked() { + return isRunningLocked() && Binder.getCallingUid() == mOwnerUID; } // Note: Return type guarantees results are deduped and sorted, which callers require. @@ -595,7 +602,7 @@ public class Vpn { // Note: This function adds to mVpnUsers but does not publish list to NetworkAgent. private void addVpnUserLocked(int userHandle) { - if (!isRunningLocked()) { + if (mVpnUsers == null) { throw new IllegalStateException("VPN is not active"); } @@ -647,7 +654,7 @@ public class Vpn { } private void removeVpnUserLocked(int userHandle) { - if (!isRunningLocked()) { + if (mVpnUsers == null) { throw new IllegalStateException("VPN is not active"); } final List<UidRange> ranges = uidRangesForUser(userHandle); @@ -767,27 +774,61 @@ public class Vpn { } public synchronized boolean addAddress(String address, int prefixLength) { - if (Binder.getCallingUid() != mOwnerUID || mInterface == null || mNetworkAgent == null) { + if (!isCallerEstablishedOwnerLocked()) { return false; } boolean success = jniAddAddress(mInterface, address, prefixLength); - if (mNetworkAgent != null) { - mNetworkAgent.sendLinkProperties(makeLinkProperties()); - } + mNetworkAgent.sendLinkProperties(makeLinkProperties()); return success; } public synchronized boolean removeAddress(String address, int prefixLength) { - if (Binder.getCallingUid() != mOwnerUID || mInterface == null || mNetworkAgent == null) { + if (!isCallerEstablishedOwnerLocked()) { return false; } boolean success = jniDelAddress(mInterface, address, prefixLength); - if (mNetworkAgent != null) { - mNetworkAgent.sendLinkProperties(makeLinkProperties()); - } + mNetworkAgent.sendLinkProperties(makeLinkProperties()); return success; } + public synchronized boolean setUnderlyingNetworks(Network[] networks) { + if (!isCallerEstablishedOwnerLocked()) { + return false; + } + if (networks == null) { + mConfig.underlyingNetworks = null; + } else { + mConfig.underlyingNetworks = new Network[networks.length]; + for (int i = 0; i < networks.length; ++i) { + if (networks[i] == null) { + mConfig.underlyingNetworks[i] = null; + } else { + mConfig.underlyingNetworks[i] = new Network(networks[i].netId); + } + } + } + return true; + } + + public synchronized Network[] getUnderlyingNetworks() { + if (!isRunningLocked()) { + return null; + } + return mConfig.underlyingNetworks; + } + + public synchronized boolean appliesToUid(int uid) { + if (!isRunningLocked()) { + return false; + } + for (UidRange uidRange : mVpnUsers) { + if (uidRange.start <= uid && uid <= uidRange.stop) { + return true; + } + } + return false; + } + private native int jniCreate(int mtu); private native String jniGetName(int tun); private native int jniSetAddresses(String interfaze, String addresses); |