From 8b5fc62d12266f8c33c022c3ef17f67aa0eef962 Mon Sep 17 00:00:00 2001 From: Paul Jensen Date: Wed, 7 May 2014 15:27:40 -0400 Subject: [PATCH] Convert Vpn from NetworkStateTracker to NetworkAgent. This eliminates the need for the ConnectivityService.VpnCallback class. This requires shifting VPNs to the new "network" netd API. VpnService.protect() is modified to no longer go through ConnectivityService. NetworkCapabilities is extended to add a transport type for VPNs and a capability requiring a non-VPN (so the default NetworkRequest isn't satisfied by a VPN). bug:15409918 Change-Id: Ic4498f1961582208add6f375ad16ce376ee9eb95 --- .../android/net/IConnectivityManager.aidl | 2 - core/java/android/net/NetworkAgent.java | 30 +++ .../java/android/net/NetworkCapabilities.java | 19 +- core/java/android/net/NetworkUtils.java | 7 + core/java/android/net/UidRange.java | 102 +++++++ core/jni/android_net_NetUtils.cpp | 6 + .../android/server/ConnectivityService.java | 248 +++++------------- .../server/connectivity/NetworkAgentInfo.java | 4 + 8 files changed, 227 insertions(+), 191 deletions(-) create mode 100644 core/java/android/net/UidRange.java 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{" +