diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index b76fc3881c..b9c6491da2 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -115,8 +115,6 @@ interface IConnectivityManager void setDataDependency(int networkType, boolean met); - boolean protectVpn(in ParcelFileDescriptor socket); - boolean prepareVpn(String oldPackage, String newPackage); ParcelFileDescriptor establishVpn(in VpnConfig config); diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 3d0874beda..41eab021ee 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -92,6 +92,20 @@ public abstract class NetworkAgent extends Handler { */ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; + /** + * Sent by the NetworkAgent to ConnectivityService to add new UID ranges + * to be forced into this Network. For VPNs only. + * obj = UidRange[] to forward + */ + public static final int EVENT_UID_RANGES_ADDED = BASE + 5; + + /** + * Sent by the NetworkAgent to ConnectivityService to remove UID ranges + * from being forced into this Network. For VPNs only. + * obj = UidRange[] to stop forwarding + */ + public static final int EVENT_UID_RANGES_REMOVED = BASE + 6; + public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score) { super(looper); @@ -193,6 +207,22 @@ public abstract class NetworkAgent extends Handler { queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score)); } + /** + * Called by the VPN code when it wants to add ranges of UIDs to be routed + * through the VPN network. + */ + public void addUidRanges(UidRange[] ranges) { + queueOrSendMessage(EVENT_UID_RANGES_ADDED, ranges); + } + + /** + * Called by the VPN code when it wants to remove ranges of UIDs from being routed + * through the VPN network. + */ + public void removeUidRanges(UidRange[] ranges) { + queueOrSendMessage(EVENT_UID_RANGES_REMOVED, ranges); + } + /** * Called when ConnectivityService has indicated they no longer want this network. * The parent factory should (previously) have received indication of the change diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 00200d0f42..239db8666a 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -64,7 +64,7 @@ public final class NetworkCapabilities implements Parcelable { * by any Network that matches all of them. */ private long mNetworkCapabilities = (1 << NET_CAPABILITY_NOT_RESTRICTED) | - (1 << NET_CAPABILITY_TRUSTED); + (1 << NET_CAPABILITY_TRUSTED) | (1 << NET_CAPABILITY_NOT_VPN); /** * Indicates this is a network that has the ability to reach the @@ -158,9 +158,15 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int NET_CAPABILITY_TRUSTED = 14; + /* + * Indicates that this network is not a VPN. This capability is set by default and should be + * explicitly cleared when creating VPN networks. + */ + public static final int NET_CAPABILITY_NOT_VPN = 15; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TRUSTED; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VPN; /** * Adds the given capability to this {@code NetworkCapability} instance. @@ -271,8 +277,13 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int TRANSPORT_ETHERNET = 3; + /** + * Indicates this network uses a VPN transport. + */ + public static final int TRANSPORT_VPN = 4; + private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR; - private static final int MAX_TRANSPORT = TRANSPORT_ETHERNET; + private static final int MAX_TRANSPORT = TRANSPORT_VPN; /** * Adds the given transport type to this {@code NetworkCapability} instance. @@ -500,6 +511,7 @@ public final class NetworkCapabilities implements Parcelable { case TRANSPORT_WIFI: transports += "WIFI"; break; case TRANSPORT_BLUETOOTH: transports += "BLUETOOTH"; break; case TRANSPORT_ETHERNET: transports += "ETHERNET"; break; + case TRANSPORT_VPN: transports += "VPN"; break; } if (++i < types.length) transports += "|"; } @@ -523,6 +535,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_INTERNET: capabilities += "INTERNET"; break; case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break; case NET_CAPABILITY_TRUSTED: capabilities += "TRUSTED"; break; + case NET_CAPABILITY_NOT_VPN: capabilities += "NOT_VPN"; break; } if (++i < types.length) capabilities += "&"; } diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index c4b17b6046..aa1e1233f1 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -154,6 +154,13 @@ public class NetworkUtils { */ public native static boolean bindSocketToNetwork(int socketfd, int netId); + /** + * Protect {@code socketfd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + public native static boolean protectFromVpn(int socketfd); + /** * Convert a IPv4 address from an integer to an InetAddress. * @param hostAddress an int corresponding to the IPv4 address in network byte order diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java new file mode 100644 index 0000000000..2e586b39b5 --- /dev/null +++ b/core/java/android/net/UidRange.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.os.UserHandle.PER_USER_RANGE; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.IllegalArgumentException; + +/** + * An inclusive range of UIDs. + * + * @hide + */ +public final class UidRange implements Parcelable { + public final int start; + public final int stop; + + public UidRange(int startUid, int stopUid) { + if (startUid < 0) throw new IllegalArgumentException("Invalid start UID."); + if (stopUid < 0) throw new IllegalArgumentException("Invalid stop UID."); + if (startUid > stopUid) throw new IllegalArgumentException("Invalid UID range."); + start = startUid; + stop = stopUid; + } + + public static UidRange createForUser(int userId) { + return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); + } + + public int getStartUser() { + return start / PER_USER_RANGE; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + start; + result = 31 * result + stop; + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof UidRange) { + UidRange other = (UidRange) o; + return start == other.start && stop == other.stop; + } + return false; + } + + @Override + public String toString() { + return start + "-" + stop; + } + + // implement the Parcelable interface + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(start); + dest.writeInt(stop); + } + + public static final Creator CREATOR = + new Creator() { + @Override + public UidRange createFromParcel(Parcel in) { + int start = in.readInt(); + int stop = in.readInt(); + + return new UidRange(start, stop); + } + @Override + public UidRange[] newArray(int size) { + return new UidRange[size]; + } + }; +} diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 6f898000bb..a75d5479e2 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -285,6 +285,11 @@ static jboolean android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, return (jboolean) !setNetworkForSocket(netId, socket); } +static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket) +{ + return (jboolean) !protectFromVpn(socket); +} + // ---------------------------------------------------------------------------- /* @@ -308,6 +313,7 @@ static JNINativeMethod gNetworkUtilMethods[] = { { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution }, { "unbindProcessToNetworkForHostResolution", "()Z", (void*) android_net_utils_unbindProcessToNetworkForHostResolution }, { "bindSocketToNetwork", "(II)Z", (void*) android_net_utils_bindSocketToNetwork }, + { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 6fc7c6ba61..ea05b98e3b 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -87,6 +87,7 @@ import android.net.ProxyDataTracker; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.SamplingDataTracker; +import android.net.UidRange; import android.net.Uri; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; @@ -235,7 +236,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { @GuardedBy("mVpns") private final SparseArray mVpns = new SparseArray(); - private VpnCallback mVpnCallback = new VpnCallback(); private boolean mLockdownEnabled; private LockdownVpnTracker mLockdownTracker; @@ -363,8 +363,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private static final int EVENT_SET_POLICY_DATA_ENABLE = 12; - private static final int EVENT_VPN_STATE_CHANGED = 13; - /** * Used internally to disable fail fast of mobile data */ @@ -3178,6 +3176,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (score != null) updateNetworkScore(nai, score.intValue()); break; } + case NetworkAgent.EVENT_UID_RANGES_ADDED: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_UID_RANGES_ADDED from unknown NetworkAgent"); + break; + } + try { + mNetd.addVpnUidRanges(nai.network.netId, (UidRange[])msg.obj); + } catch (RemoteException e) { + } + break; + } + case NetworkAgent.EVENT_UID_RANGES_REMOVED: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_UID_RANGES_REMOVED from unknown NetworkAgent"); + break; + } + try { + mNetd.removeVpnUidRanges(nai.network.netId, (UidRange[])msg.obj); + } catch (RemoteException e) { + } + break; + } case NetworkMonitor.EVENT_NETWORK_VALIDATED: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; handleConnectionValidated(nai); @@ -3459,12 +3481,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (affectedNetwork != null) { // check if this network still has live requests - otherwise, tear down // TODO - probably push this to the NF/NA - boolean keep = false; - for (int i = 0; i < affectedNetwork.networkRequests.size(); i++) { + boolean keep = affectedNetwork.isVPN(); + for (int i = 0; i < affectedNetwork.networkRequests.size() && !keep; i++) { NetworkRequest r = affectedNetwork.networkRequests.valueAt(i); if (mNetworkRequests.get(r).isRequest) { keep = true; - break; } } if (keep == false) { @@ -3544,12 +3565,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleSetPolicyDataEnable(networkType, enabled); break; } - case EVENT_VPN_STATE_CHANGED: { - if (mLockdownTracker != null) { - mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj); - } - break; - } case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: { int tag = mEnableFailFastMobileDataTag.get(); if (msg.arg1 == tag) { @@ -4056,36 +4071,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { return value; } - /** - * Protect a socket from VPN routing rules. This method is used by - * VpnBuilder and not available in ConnectivityManager. Permissions - * are checked in Vpn class. - * @hide - */ - @Override - public boolean protectVpn(ParcelFileDescriptor socket) { - throwIfLockdownEnabled(); - try { - int type = mActiveDefaultNetwork; - int user = UserHandle.getUserId(Binder.getCallingUid()); - if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) { - synchronized(mVpns) { - mVpns.get(user).protect(socket); - } - return true; - } - } catch (Exception e) { - // ignore - } finally { - try { - socket.close(); - } catch (Exception e) { - // ignore - } - } - return false; - } - /** * Prepare for a VPN application. This method is used by VpnDialogs * and not available in ConnectivityManager. Permissions are checked @@ -4180,144 +4165,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - /** - * Callback for VPN subsystem. Currently VPN is not adapted to the service - * through NetworkStateTracker since it works differently. For example, it - * needs to override DNS servers but never takes the default routes. It - * relies on another data network, and it could keep existing connections - * alive after reconnecting, switching between networks, or even resuming - * from deep sleep. Calls from applications should be done synchronously - * to avoid race conditions. As these are all hidden APIs, refactoring can - * be done whenever a better abstraction is developed. - */ - public class VpnCallback { - private VpnCallback() { - } - - public void onStateChanged(NetworkInfo info) { - mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget(); - } - - public void override(String iface, List dnsServers, List searchDomains) { - if (dnsServers == null) { - restore(); - return; - } - - // Convert DNS servers into addresses. - List addresses = new ArrayList(); - for (String address : dnsServers) { - // Double check the addresses and remove invalid ones. - try { - addresses.add(InetAddress.parseNumericAddress(address)); - } catch (Exception e) { - // ignore - } - } - if (addresses.isEmpty()) { - restore(); - return; - } - - // Concatenate search domains into a string. - StringBuilder buffer = new StringBuilder(); - if (searchDomains != null) { - for (String domain : searchDomains) { - buffer.append(domain).append(' '); - } - } - String domains = buffer.toString().trim(); - - // Apply DNS changes. - synchronized (mDnsLock) { - // TODO: Re-enable this when the netId of the VPN is known. - // updateDnsLocked("VPN", netId, addresses, domains); - } - - // Temporarily disable the default proxy (not global). - synchronized (mProxyLock) { - mDefaultProxyDisabled = true; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(null); - } - } - - // TODO: support proxy per network. - } - - public void restore() { - synchronized (mProxyLock) { - mDefaultProxyDisabled = false; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(mDefaultProxy); - } - } - } - - public void protect(ParcelFileDescriptor socket) { - try { - final int mark = mNetd.getMarkForProtect(); - NetworkUtils.markSocket(socket.getFd(), mark); - } catch (RemoteException e) { - } - } - - public void setRoutes(String interfaze, List routes) { - for (RouteInfo route : routes) { - try { - mNetd.setMarkedForwardingRoute(interfaze, route); - } catch (RemoteException e) { - } - } - } - - public void setMarkedForwarding(String interfaze) { - try { - mNetd.setMarkedForwarding(interfaze); - } catch (RemoteException e) { - } - } - - public void clearMarkedForwarding(String interfaze) { - try { - mNetd.clearMarkedForwarding(interfaze); - } catch (RemoteException e) { - } - } - - public void addUserForwarding(String interfaze, int uid, boolean forwardDns) { - int uidStart = uid * UserHandle.PER_USER_RANGE; - int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1; - addUidForwarding(interfaze, uidStart, uidEnd, forwardDns); - } - - public void clearUserForwarding(String interfaze, int uid, boolean forwardDns) { - int uidStart = uid * UserHandle.PER_USER_RANGE; - int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1; - clearUidForwarding(interfaze, uidStart, uidEnd, forwardDns); - } - - public void addUidForwarding(String interfaze, int uidStart, int uidEnd, - boolean forwardDns) { - // TODO: Re-enable this when the netId of the VPN is known. - // try { - // mNetd.setUidRangeRoute(netId, uidStart, uidEnd, forwardDns); - // } catch (RemoteException e) { - // } - - } - - public void clearUidForwarding(String interfaze, int uidStart, int uidEnd, - boolean forwardDns) { - // TODO: Re-enable this when the netId of the VPN is known. - // try { - // mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd); - // } catch (RemoteException e) { - // } - - } - } - @Override public boolean updateLockdownVpn() { if (Binder.getCallingUid() != Process.SYSTEM_UID) { @@ -5361,9 +5208,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Starting user already has a VPN"); return; } - userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId); + userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, this, userId); mVpns.put(userId, userVpn); - userVpn.startMonitoring(mContext, mTrackerHandler); } } @@ -5885,7 +5731,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Unknown NetworkAgentInfo in handleConnectionValidated"); return; } - boolean keep = false; + boolean keep = newNetwork.isVPN(); boolean isNewDefault = false; if (DBG) log("handleConnectionValidated for "+newNetwork.name()); // check if any NetworkRequest wants this NetworkAgent @@ -5947,8 +5793,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } for (NetworkAgentInfo nai : affectedNetworks) { - boolean teardown = true; - for (int i = 0; i < nai.networkRequests.size(); i++) { + boolean teardown = !nai.isVPN(); + for (int i = 0; i < nai.networkRequests.size() && teardown; i++) { NetworkRequest nr = nai.networkRequests.valueAt(i); try { if (mNetworkRequests.get(nr).isRequest) { @@ -6031,6 +5877,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { oldInfo = networkAgent.networkInfo; networkAgent.networkInfo = newInfo; } + if (networkAgent.isVPN() && mLockdownTracker != null) { + mLockdownTracker.onVpnStateChanged(newInfo); + } if (oldInfo != null && oldInfo.getState() == state) { if (VDBG) log("ignoring duplicate network state non-change"); @@ -6049,7 +5898,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // CONNECTING and back (like wifi on DHCP renew). // TODO: keep track of which networks we've created, or ask netd // to tell us whether we've already created this network or not. - mNetd.createNetwork(networkAgent.network.netId); + if (networkAgent.isVPN()) { + mNetd.createVirtualNetwork(networkAgent.network.netId, + !networkAgent.linkProperties.getDnsServers().isEmpty()); + } else { + mNetd.createPhysicalNetwork(networkAgent.network.netId); + } } catch (Exception e) { loge("Error creating network " + networkAgent.network.netId + ": " + e.getMessage()); @@ -6059,9 +5913,31 @@ public class ConnectivityService extends IConnectivityManager.Stub { updateLinkProperties(networkAgent, null); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); + if (networkAgent.isVPN()) { + // Temporarily disable the default proxy (not global). + synchronized (mProxyLock) { + if (!mDefaultProxyDisabled) { + mDefaultProxyDisabled = true; + if (mGlobalProxy == null && mDefaultProxy != null) { + sendProxyBroadcast(null); + } + } + } + // TODO: support proxy per network. + } } else if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.SUSPENDED) { networkAgent.asyncChannel.disconnect(); + if (networkAgent.isVPN()) { + synchronized (mProxyLock) { + if (mDefaultProxyDisabled) { + mDefaultProxyDisabled = false; + if (mGlobalProxy == null && mDefaultProxy != null) { + sendProxyBroadcast(mDefaultProxy); + } + } + } + } } } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 13328983d3..10bdba060e 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -69,6 +69,10 @@ public class NetworkAgentInfo { networkRequests.put(networkRequest.requestId, networkRequest); } + public boolean isVPN() { + return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); + } + public String toString() { return "NetworkAgentInfo{ ni{" + networkInfo + "} network{" + network + "} lp{" +