summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Sreeram Ramachandran <sreeram@google.com> 2014-11-11 16:09:21 -0800
committer Sreeram Ramachandran <sreeram@google.com> 2014-11-21 09:26:18 -0800
commitc2c0beab79a907f63e109eefe2a5aabcf2e3fd8f (patch)
treea0a879c50b307f171dd2b0dc3a5abcb2be4afbc1
parent21b5ee3f0e39be4a79bcfb2b79b0529f75f5cb58 (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.txt2
-rw-r--r--core/java/android/net/IConnectivityManager.aidl1
-rw-r--r--core/java/android/net/VpnService.java55
-rw-r--r--core/java/com/android/internal/net/VpnConfig.java4
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java32
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java63
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 &mdash; such as the socket(s) passed to {@link #protect(int)} &mdash;
+ * 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);