diff options
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | core/java/android/net/IConnectivityManager.aidl | 2 | ||||
-rw-r--r-- | core/java/android/net/NetworkAgent.java | 30 | ||||
-rw-r--r-- | core/java/android/net/NetworkCapabilities.java | 19 | ||||
-rw-r--r-- | core/java/android/net/NetworkUtils.java | 7 | ||||
-rw-r--r-- | core/java/android/net/UidRange.aidl | 24 | ||||
-rw-r--r-- | core/java/android/net/UidRange.java | 102 | ||||
-rw-r--r-- | core/java/android/net/VpnService.java | 15 | ||||
-rw-r--r-- | core/java/android/os/INetworkManagementService.aidl | 42 | ||||
-rw-r--r-- | core/jni/android_net_NetUtils.cpp | 6 | ||||
-rw-r--r-- | services/core/java/com/android/server/ConnectivityService.java | 248 | ||||
-rw-r--r-- | services/core/java/com/android/server/NetworkManagementService.java | 106 | ||||
-rw-r--r-- | services/core/java/com/android/server/connectivity/NetworkAgentInfo.java | 4 | ||||
-rw-r--r-- | services/core/java/com/android/server/connectivity/NetworkMonitor.java | 3 | ||||
-rw-r--r-- | services/core/java/com/android/server/connectivity/Vpn.java | 431 | ||||
-rw-r--r-- | services/core/jni/com_android_server_connectivity_Vpn.cpp | 124 |
16 files changed, 565 insertions, 600 deletions
diff --git a/api/current.txt b/api/current.txt index bc436c3..092061c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16506,6 +16506,7 @@ package android.net { field public static final int NET_CAPABILITY_MMS = 0; // 0x0 field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd + field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf field public static final int NET_CAPABILITY_RCS = 8; // 0x8 field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe @@ -16514,6 +16515,7 @@ package android.net { field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2 field public static final int TRANSPORT_CELLULAR = 0; // 0x0 field public static final int TRANSPORT_ETHERNET = 3; // 0x3 + field public static final int TRANSPORT_VPN = 4; // 0x4 field public static final int TRANSPORT_WIFI = 1; // 0x1 } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index b76fc38..b9c6491 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 3d0874b..41eab02 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); @@ -194,6 +208,22 @@ public abstract class NetworkAgent extends Handler { } /** + * 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 * as well, either canceling NetworkRequests or altering their score such that this diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 00200d0..239db86 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 c4b17b6..aa1e123 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -155,6 +155,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.aidl b/core/java/android/net/UidRange.aidl new file mode 100644 index 0000000..f9be628 --- /dev/null +++ b/core/java/android/net/UidRange.aidl @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * An inclusive range of UIDs. + * + * {@hide} + */ +parcelable UidRange; diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java new file mode 100644 index 0000000..2e586b3 --- /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<UidRange> CREATOR = + new Creator<UidRange>() { + @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/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 7c62bf6..1d89eae 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -24,6 +24,7 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.net.NetworkUtils; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; @@ -168,19 +169,7 @@ public class VpnService extends Service { * @return {@code true} on success. */ public boolean protect(int socket) { - ParcelFileDescriptor dup = null; - try { - dup = ParcelFileDescriptor.fromFd(socket); - return getService().protectVpn(dup); - } catch (Exception e) { - return false; - } finally { - try { - dup.close(); - } catch (Exception e) { - // ignore - } - } + return NetworkUtils.protectFromVpn(socket); } /** diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index eb9ba13..d997e44 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -22,6 +22,7 @@ import android.net.INetworkManagementEventObserver; import android.net.LinkAddress; import android.net.NetworkStats; import android.net.RouteInfo; +import android.net.UidRange; import android.net.wifi.WifiConfiguration; import android.os.INetworkActivityListener; @@ -325,28 +326,14 @@ interface INetworkManagementService void setFirewallUidRule(int uid, boolean allow); /** - * Set all packets from users [uid_start,uid_end] to go through interface iface - * iface must already be set for marked forwarding by {@link setMarkedForwarding} + * Set all packets from users in ranges to go through VPN specified by netId. */ - void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns); + void addVpnUidRanges(int netId, in UidRange[] ranges); /** - * Clears the special routing rules for users [uid_start,uid_end] + * Clears the special VPN rules for users in ranges and VPN specified by netId. */ - void clearUidRangeRoute(String iface, int uid_start, int uid_end); - - /** - * Setup an interface for routing packets marked by {@link setUidRangeRoute} - * - * This sets up a dedicated routing table for packets marked for {@code iface} and adds - * source-NAT rules so that the marked packets have the correct source address. - */ - void setMarkedForwarding(String iface); - - /** - * Removes marked forwarding for an interface - */ - void clearMarkedForwarding(String iface); + void removeVpnUidRanges(int netId, in UidRange[] ranges); /** * Get the SO_MARK associated with routing packets for user {@code uid} @@ -410,9 +397,14 @@ interface INetworkManagementService boolean isNetworkActive(); /** - * Setup a new network. + * Setup a new physical network. + */ + void createPhysicalNetwork(int netId); + + /** + * Setup a new VPN. */ - void createNetwork(int netId); + void createVirtualNetwork(int netId, boolean hasDNS); /** * Remove a network. @@ -437,4 +429,14 @@ interface INetworkManagementService void setPermission(boolean internal, boolean changeNetState, in int[] uids); void clearPermission(in int[] uids); + + /** + * Allow UID to call protect(). + */ + void allowProtect(int uid); + + /** + * Deny UID from calling protect(). + */ + void denyProtect(int uid); } diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 6f89800..a75d547 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 6fc7c6b..ea05b98 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<Vpn> mVpns = new SparseArray<Vpn>(); - 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) { @@ -4057,36 +4072,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** - * 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 * in Vpn class. @@ -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<String> dnsServers, List<String> searchDomains) { - if (dnsServers == null) { - restore(); - return; - } - - // Convert DNS servers into addresses. - List<InetAddress> addresses = new ArrayList<InetAddress>(); - 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<RouteInfo> 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/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index f9c7a78..c9f40cf 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -46,6 +46,7 @@ import android.net.LinkAddress; import android.net.NetworkStats; import android.net.NetworkUtils; import android.net.RouteInfo; +import android.net.UidRange; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.os.BatteryStats; @@ -90,6 +91,7 @@ import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -116,6 +118,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private static final String DEFAULT = "default"; private static final String SECONDARY = "secondary"; + private static final int MAX_UID_RANGES_PER_COMMAND = 10; + /** * Name representing {@link #setGlobalAlert(long)} limit when delivered to * {@link INetworkManagementEventObserver#limitReached(String, String)}. @@ -1702,44 +1706,46 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns) { + public void addVpnUidRanges(int netId, UidRange[] ranges) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", - "uid", "add", iface, uid_start, uid_end, forward_dns ? 1 : 0); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); - } - } - - @Override - public void clearUidRangeRoute(String iface, int uid_start, int uid_end) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", - "uid", "remove", iface, uid_start, uid_end, 0); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); - } - } - - @Override - public void setMarkedForwarding(String iface) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", "rule", "add", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND]; + argv[0] = "users"; + argv[1] = "add"; + argv[2] = netId; + int argc = 3; + // Avoid overly long commands by limiting number of UID ranges per command. + for (int i = 0; i < ranges.length; i++) { + argv[argc++] = ranges[i].toString(); + if (i == (ranges.length - 1) || argc == argv.length) { + try { + mConnector.execute("network", Arrays.copyOf(argv, argc)); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + argc = 3; + } } } @Override - public void clearMarkedForwarding(String iface) { + public void removeVpnUidRanges(int netId, UidRange[] ranges) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", "rule", "remove", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND]; + argv[0] = "users"; + argv[1] = "remove"; + argv[2] = netId; + int argc = 3; + // Avoid overly long commands by limiting number of UID ranges per command. + for (int i = 0; i < ranges.length; i++) { + argv[argc++] = ranges[i].toString(); + if (i == (ranges.length - 1) || argc == argv.length) { + try { + mConnector.execute("network", Arrays.copyOf(argv, argc)); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + argc = 3; + } } } @@ -2015,7 +2021,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void createNetwork(int netId) { + public void createPhysicalNetwork(int netId) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { @@ -2026,6 +2032,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override + public void createVirtualNetwork(int netId, boolean hasDNS) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + try { + mConnector.execute("network", "create", netId, "vpn", hasDNS ? "1" : "0"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override public void removeNetwork(int netId) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); @@ -2143,4 +2160,27 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw e.rethrowAsParcelableException(); } } + + @Override + public void allowProtect(int uid) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + try { + mConnector.execute("network", "protect", "allow", uid); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void denyProtect(int uid) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + try { + mConnector.execute("network", "protect", "deny", uid); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 1332898..10bdba0 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{" + diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index 0d3b501..6fb8570 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -319,6 +319,9 @@ public class NetworkMonitor extends StateMachine { case CMD_REEVALUATE: if (message.arg1 != mReevaluateToken) break; + if (mNetworkAgentInfo.isVPN()) { + transitionTo(mValidatedState); + } // If network provides no internet connectivity adjust evaluation. if (!mNetworkAgentInfo.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET)) { diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index df12995..d15254b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -30,6 +30,7 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.graphics.Bitmap; @@ -43,14 +44,18 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.net.NetworkAgent; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; import android.net.NetworkUtils; import android.net.RouteInfo; -import android.net.NetworkInfo.DetailedState; +import android.net.UidRange; import android.os.Binder; import android.os.FileUtils; import android.os.IBinder; import android.os.INetworkManagementService; +import android.os.Looper; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -69,7 +74,6 @@ import com.android.internal.R; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; -import com.android.server.ConnectivityService.VpnCallback; import com.android.server.net.BaseNetworkObserver; import java.io.File; @@ -78,7 +82,9 @@ import java.io.OutputStream; import java.net.InetAddress; import java.net.Inet4Address; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import libcore.io.IoUtils; @@ -86,16 +92,18 @@ import libcore.io.IoUtils; /** * @hide */ -public class Vpn extends BaseNetworkStateTracker { +public class Vpn { + private static final String NETWORKTYPE = "VPN"; private static final String TAG = "Vpn"; private static final boolean LOGD = true; - + // TODO: create separate trackers for each unique VPN to support // automated reconnection - private final VpnCallback mCallback; - - private String mPackage = VpnConfig.LEGACY_VPN; + private Context mContext; + private NetworkInfo mNetworkInfo; + private String mPackage; + private int mOwnerUID; private String mInterface; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; @@ -103,22 +111,29 @@ public class Vpn extends BaseNetworkStateTracker { private volatile boolean mEnableNotif = true; private volatile boolean mEnableTeardown = true; private final IConnectivityManager mConnService; + private final INetworkManagementService mNetd; private VpnConfig mConfig; + private NetworkAgent mNetworkAgent; + private final Looper mLooper; + private final NetworkCapabilities mNetworkCapabilities; /* list of users using this VPN. */ @GuardedBy("this") - private SparseBooleanArray mVpnUsers = null; + private List<UidRange> mVpnUsers = null; private BroadcastReceiver mUserIntentReceiver = null; private final int mUserId; - public Vpn(Context context, VpnCallback callback, INetworkManagementService netService, + public Vpn(Looper looper, Context context, INetworkManagementService netService, IConnectivityManager connService, int userId) { - super(ConnectivityManager.TYPE_VPN); mContext = context; - mCallback = callback; + mNetd = netService; mConnService = connService; mUserId = userId; + mLooper = looper; + + mPackage = VpnConfig.LEGACY_VPN; + mOwnerUID = getAppUid(mPackage); try { netService.registerObserver(mObserver); @@ -149,6 +164,12 @@ public class Vpn extends BaseNetworkStateTracker { mContext.registerReceiverAsUser( mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); } + + mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, ""); + // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332 + mNetworkCapabilities = new NetworkCapabilities(); + mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN); + mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); } /** @@ -168,35 +189,15 @@ public class Vpn extends BaseNetworkStateTracker { mEnableTeardown = enableTeardown; } - @Override - protected void startMonitoringInternal() { - // Ignored; events are sent through callbacks for now - } - - @Override - public boolean teardown() { - // TODO: finish migration to unique tracker for each VPN - throw new UnsupportedOperationException(); - } - - @Override - public boolean reconnect() { - // TODO: finish migration to unique tracker for each VPN - throw new UnsupportedOperationException(); - } - - @Override - public String getTcpBufferSizesPropName() { - return PROP_TCP_BUFFER_UNKNOWN; - } - /** * Update current state, dispaching event to listeners. */ private void updateState(DetailedState detailedState, String reason) { if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason); mNetworkInfo.setDetailedState(detailedState, reason, null); - mCallback.onStateChanged(new NetworkInfo(mNetworkInfo)); + if (mNetworkAgent != null) { + mNetworkAgent.sendNetworkInfo(mNetworkInfo); + } } /** @@ -234,22 +235,10 @@ public class Vpn extends BaseNetworkStateTracker { // Reset the interface and hide the notification. if (mInterface != null) { - final long token = Binder.clearCallingIdentity(); - try { - mCallback.restore(); - final int size = mVpnUsers.size(); - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - for (int i = 0; i < size; i++) { - int user = mVpnUsers.keyAt(i); - mCallback.clearUserForwarding(mInterface, user, forwardDns); - hideNotification(user); - } - - mCallback.clearMarkedForwarding(mInterface); - } finally { - Binder.restoreCallingIdentity(token); + for (UidRange uidRange : mVpnUsers) { + hideNotification(uidRange.getStartUser()); } + agentDisconnect(); jniReset(mInterface); mInterface = null; mVpnUsers = null; @@ -270,34 +259,125 @@ public class Vpn extends BaseNetworkStateTracker { mLegacyVpnRunner = null; } + long token = Binder.clearCallingIdentity(); + try { + mNetd.denyProtect(mOwnerUID); + } catch (Exception e) { + Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e); + } finally { + Binder.restoreCallingIdentity(token); + } + Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); mPackage = newPackage; + mOwnerUID = getAppUid(newPackage); + token = Binder.clearCallingIdentity(); + try { + mNetd.allowProtect(mOwnerUID); + } catch (Exception e) { + Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e); + } finally { + Binder.restoreCallingIdentity(token); + } mConfig = null; updateState(DetailedState.IDLE, "prepare"); return true; } - /** - * Protect a socket from VPN rules by binding it to the main routing table. - * The socket is NOT closed by this method. - * - * @param socket The socket to be bound. - */ - public void protect(ParcelFileDescriptor socket) throws Exception { - + private int getAppUid(String app) { + if (app == VpnConfig.LEGACY_VPN) { + return Process.myUid(); + } PackageManager pm = mContext.getPackageManager(); - int appUid = pm.getPackageUid(mPackage, mUserId); - if (Binder.getCallingUid() != appUid) { - throw new SecurityException("Unauthorized Caller"); + int result; + try { + result = pm.getPackageUid(app, mUserId); + } catch (NameNotFoundException e) { + result = -1; } - // protect the socket from routing rules - final long token = Binder.clearCallingIdentity(); + return result; + } + + public NetworkInfo getNetworkInfo() { + return mNetworkInfo; + } + + private void agentConnect() { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(mInterface); + boolean hasDefaultRoute = false; + for (RouteInfo route : mConfig.routes) { + lp.addRoute(route); + if (route.isDefaultRoute()) hasDefaultRoute = true; + } + if (hasDefaultRoute) { + mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } else { + mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + if (mConfig.dnsServers != null) { + for (String dnsServer : mConfig.dnsServers) { + lp.addDnsServer(InetAddress.parseNumericAddress(dnsServer)); + } + } + // Concatenate search domains into a string. + StringBuilder buffer = new StringBuilder(); + if (mConfig.searchDomains != null) { + for (String domain : mConfig.searchDomains) { + buffer.append(domain).append(' '); + } + } + lp.setDomains(buffer.toString().trim()); + mNetworkInfo.setIsAvailable(true); + mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); + long token = Binder.clearCallingIdentity(); try { - mCallback.protect(socket); + mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE, + mNetworkInfo, mNetworkCapabilities, lp, 0) { + public void unwanted() { + // We are user controlled, not driven by NetworkRequest. + }; + }; } finally { Binder.restoreCallingIdentity(token); } + addVpnUserLocked(mUserId); + // If we are owner assign all Restricted Users to this VPN + if (mUserId == UserHandle.USER_OWNER) { + token = Binder.clearCallingIdentity(); + List<UserInfo> users; + try { + users = UserManager.get(mContext).getUsers(); + } finally { + Binder.restoreCallingIdentity(token); + } + for (UserInfo user : users) { + if (user.isRestricted()) { + addVpnUserLocked(user.id); + } + } + } + mNetworkAgent.addUidRanges(mVpnUsers.toArray(new UidRange[mVpnUsers.size()])); + } + + private void agentDisconnect(NetworkInfo networkInfo, NetworkAgent networkAgent) { + networkInfo.setIsAvailable(false); + networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null); + if (networkAgent != null) { + networkAgent.sendNetworkInfo(networkInfo); + } + } + + private void agentDisconnect(NetworkAgent networkAgent) { + NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo); + agentDisconnect(networkInfo, networkAgent); + } + private void agentDisconnect() { + if (mNetworkInfo.isConnected()) { + agentDisconnect(mNetworkInfo, mNetworkAgent); + mNetworkAgent = null; + } } /** @@ -311,14 +391,7 @@ public class Vpn extends BaseNetworkStateTracker { public synchronized ParcelFileDescriptor establish(VpnConfig config) { // Check if the caller is already prepared. UserManager mgr = UserManager.get(mContext); - PackageManager pm = mContext.getPackageManager(); - ApplicationInfo app = null; - try { - app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); - if (Binder.getCallingUid() != app.uid) { - return null; - } - } catch (Exception e) { + if (Binder.getCallingUid() != mOwnerUID) { return null; } // Check if the service is properly declared. @@ -350,7 +423,9 @@ public class Vpn extends BaseNetworkStateTracker { VpnConfig oldConfig = mConfig; String oldInterface = mInterface; Connection oldConnection = mConnection; - SparseBooleanArray oldUsers = mVpnUsers; + NetworkAgent oldNetworkAgent = mNetworkAgent; + mNetworkAgent = null; + List<UidRange> oldUsers = mVpnUsers; // Configure the interface. Abort if any of these steps fails. ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); @@ -382,67 +457,27 @@ public class Vpn extends BaseNetworkStateTracker { mConfig = config; // Set up forwarding and DNS rules. - mVpnUsers = new SparseBooleanArray(); - token = Binder.clearCallingIdentity(); - try { - mCallback.setMarkedForwarding(mInterface); - mCallback.setRoutes(mInterface, config.routes); - mCallback.override(mInterface, config.dnsServers, config.searchDomains); - addVpnUserLocked(mUserId); - // If we are owner assign all Restricted Users to this VPN - if (mUserId == UserHandle.USER_OWNER) { - for (UserInfo user : mgr.getUsers()) { - if (user.isRestricted()) { - try { - addVpnUserLocked(user.id); - } catch (Exception e) { - Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN"); - } - } - } - } - } finally { - Binder.restoreCallingIdentity(token); - } + mVpnUsers = new ArrayList<UidRange>(); + agentConnect(); if (oldConnection != null) { mContext.unbindService(oldConnection); } + // Remove the old tun's user forwarding rules + // The new tun's user rules have already been added so they will take over + // as rules are deleted. This prevents data leakage as the rules are moved over. + agentDisconnect(oldNetworkAgent); if (oldInterface != null && !oldInterface.equals(interfaze)) { - // Remove the old tun's user forwarding rules - // The new tun's user rules have already been added so they will take over - // as rules are deleted. This prevents data leakage as the rules are moved over. - token = Binder.clearCallingIdentity(); - try { - final int size = oldUsers.size(); - final boolean forwardDns = (oldConfig.dnsServers != null && - oldConfig.dnsServers.size() != 0); - for (int i = 0; i < size; i++) { - int user = oldUsers.keyAt(i); - mCallback.clearUserForwarding(oldInterface, user, forwardDns); - } - mCallback.clearMarkedForwarding(oldInterface); - } finally { - Binder.restoreCallingIdentity(token); - } jniReset(oldInterface); } } catch (RuntimeException e) { - updateState(DetailedState.FAILED, "establish"); IoUtils.closeQuietly(tun); - // make sure marked forwarding is cleared if it was set - token = Binder.clearCallingIdentity(); - try { - mCallback.clearMarkedForwarding(mInterface); - } catch (Exception ingored) { - // ignored - } finally { - Binder.restoreCallingIdentity(token); - } + agentDisconnect(); // restore old state mConfig = oldConfig; mConnection = oldConnection; mVpnUsers = oldUsers; + mNetworkAgent = oldNetworkAgent; mInterface = oldInterface; throw e; } @@ -469,29 +504,27 @@ public class Vpn extends BaseNetworkStateTracker { return mVpnUsers != null; } + // Note: This function adds to mVpnUsers but does not publish list to NetworkAgent. private void addVpnUserLocked(int user) { - enforceControlPermission(); - if (!isRunningLocked()) { throw new IllegalStateException("VPN is not active"); } - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - // add the user - mCallback.addUserForwarding(mInterface, user, forwardDns); - mVpnUsers.put(user, true); + mVpnUsers.add(UidRange.createForUser(user)); // show the notification if (!mPackage.equals(VpnConfig.LEGACY_VPN)) { // Load everything for the user's notification PackageManager pm = mContext.getPackageManager(); ApplicationInfo app = null; + final long token = Binder.clearCallingIdentity(); try { app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); } catch (RemoteException e) { throw new IllegalStateException("Invalid application"); + } finally { + Binder.restoreCallingIdentity(token); } String label = app.loadLabel(pm).toString(); // Load the icon and convert it into a bitmap. @@ -515,15 +548,14 @@ public class Vpn extends BaseNetworkStateTracker { } private void removeVpnUserLocked(int user) { - enforceControlPermission(); - if (!isRunningLocked()) { throw new IllegalStateException("VPN is not active"); } - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - mCallback.clearUserForwarding(mInterface, user, forwardDns); - mVpnUsers.delete(user); + UidRange uidRange = UidRange.createForUser(user); + if (mNetworkAgent != null) { + mNetworkAgent.removeUidRanges(new UidRange[] { uidRange }); + } + mVpnUsers.remove(uidRange); hideNotification(user); } @@ -535,6 +567,10 @@ public class Vpn extends BaseNetworkStateTracker { if (user.isRestricted()) { try { addVpnUserLocked(userId); + if (mNetworkAgent != null) { + UidRange uidRange = UidRange.createForUser(userId); + mNetworkAgent.addUidRanges(new UidRange[] { uidRange }); + } } catch (Exception e) { Log.wtf(TAG, "Failed to add restricted user to owner", e); } @@ -588,28 +624,15 @@ public class Vpn extends BaseNetworkStateTracker { public void interfaceRemoved(String interfaze) { synchronized (Vpn.this) { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { - final long token = Binder.clearCallingIdentity(); - try { - final int size = mVpnUsers.size(); - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - for (int i = 0; i < size; i++) { - int user = mVpnUsers.keyAt(i); - mCallback.clearUserForwarding(mInterface, user, forwardDns); - hideNotification(user); - } - mVpnUsers = null; - mCallback.clearMarkedForwarding(mInterface); - - mCallback.restore(); - } finally { - Binder.restoreCallingIdentity(token); + for (UidRange uidRange : mVpnUsers) { + hideNotification(uidRange.getStartUser()); } + mVpnUsers = null; mInterface = null; if (mConnection != null) { mContext.unbindService(mConnection); mConnection = null; - updateState(DetailedState.DISCONNECTED, "interfaceRemoved"); + agentDisconnect(); } else if (mLegacyVpnRunner != null) { mLegacyVpnRunner.exit(); mLegacyVpnRunner = null; @@ -658,27 +681,32 @@ public class Vpn extends BaseNetworkStateTracker { private void showNotification(String label, Bitmap icon, int user) { if (!mEnableNotif) return; - mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); - - NotificationManager nm = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - if (nm != null) { - String title = (label == null) ? mContext.getString(R.string.vpn_title) : - mContext.getString(R.string.vpn_title_long, label); - String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : - mContext.getString(R.string.vpn_text_long, mConfig.session); - - Notification notification = new Notification.Builder(mContext) - .setSmallIcon(R.drawable.vpn_connected) - .setLargeIcon(icon) - .setContentTitle(title) - .setContentText(text) - .setContentIntent(mStatusIntent) - .setDefaults(0) - .setOngoing(true) - .build(); - nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); + final long token = Binder.clearCallingIdentity(); + try { + mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); + + NotificationManager nm = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + + if (nm != null) { + String title = (label == null) ? mContext.getString(R.string.vpn_title) : + mContext.getString(R.string.vpn_title_long, label); + String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : + mContext.getString(R.string.vpn_text_long, mConfig.session); + + Notification notification = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.vpn_connected) + .setLargeIcon(icon) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(mStatusIntent) + .setDefaults(0) + .setOngoing(true) + .build(); + nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); + } + } finally { + Binder.restoreCallingIdentity(token); } } @@ -690,14 +718,18 @@ public class Vpn extends BaseNetworkStateTracker { mContext.getSystemService(Context.NOTIFICATION_SERVICE); if (nm != null) { - nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); + final long token = Binder.clearCallingIdentity(); + try { + nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); + } finally { + Binder.restoreCallingIdentity(token); + } } } private native int jniCreate(int mtu); private native String jniGetName(int tun); private native int jniSetAddresses(String interfaze, String addresses); - private native int jniSetRoutes(String interfaze, String routes); private native void jniReset(String interfaze); private native int jniCheck(String interfaze); @@ -959,7 +991,7 @@ public class Vpn extends BaseNetworkStateTracker { for (LocalSocket socket : mSockets) { IoUtils.closeQuietly(socket); } - updateState(DetailedState.DISCONNECTED, "exit"); + agentDisconnect(); try { mContext.unregisterReceiver(mBroadcastReceiver); } catch (IllegalArgumentException e) {} @@ -1018,7 +1050,7 @@ public class Vpn extends BaseNetworkStateTracker { restart = restart || (arguments != null); } if (!restart) { - updateState(DetailedState.DISCONNECTED, "execute"); + agentDisconnect(); return; } updateState(DetailedState.CONNECTING, "execute"); @@ -1129,15 +1161,6 @@ public class Vpn extends BaseNetworkStateTracker { } } - // Set the routes. - long token = Binder.clearCallingIdentity(); - try { - mCallback.setMarkedForwarding(mConfig.interfaze); - mCallback.setRoutes(mConfig.interfaze, mConfig.routes); - } finally { - Binder.restoreCallingIdentity(token); - } - // Here is the last step and it must be done synchronously. synchronized (Vpn.this) { // Set the start time @@ -1153,44 +1176,14 @@ public class Vpn extends BaseNetworkStateTracker { // Now INetworkManagementEventObserver is watching our back. mInterface = mConfig.interfaze; - mVpnUsers = new SparseBooleanArray(); - - token = Binder.clearCallingIdentity(); - try { - mCallback.override(mInterface, mConfig.dnsServers, mConfig.searchDomains); - addVpnUserLocked(mUserId); - } finally { - Binder.restoreCallingIdentity(token); - } + mVpnUsers = new ArrayList<UidRange>(); + + agentConnect(); - // Assign all restircted users to this VPN - // (Legacy VPNs are Owner only) - UserManager mgr = UserManager.get(mContext); - token = Binder.clearCallingIdentity(); - try { - for (UserInfo user : mgr.getUsers()) { - if (user.isRestricted()) { - try { - addVpnUserLocked(user.id); - } catch (Exception e) { - Log.wtf(TAG, "Failed to add user " + user.id - + " to owner's VPN"); - } - } - } - } finally { - Binder.restoreCallingIdentity(token); - } Log.i(TAG, "Connected!"); - updateState(DetailedState.CONNECTED, "execute"); } } catch (Exception e) { Log.i(TAG, "Aborting", e); - // make sure the routing is cleared - try { - mCallback.clearMarkedForwarding(mConfig.interfaze); - } catch (Exception ignored) { - } exit(); } finally { // Kill the daemons if they fail to stop. @@ -1202,7 +1195,7 @@ public class Vpn extends BaseNetworkStateTracker { // Do not leave an unstable state. if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) { - updateState(DetailedState.FAILED, "execute"); + agentDisconnect(); } } } @@ -1232,7 +1225,7 @@ public class Vpn extends BaseNetworkStateTracker { SystemService.stop(daemon); } - updateState(DetailedState.DISCONNECTED, "babysit"); + agentDisconnect(); } } } diff --git a/services/core/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp index bf34a74..6031906 100644 --- a/services/core/jni/com_android_server_connectivity_Vpn.cpp +++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp @@ -187,96 +187,6 @@ static int set_addresses(const char *name, const char *addresses) return count; } -static int set_routes(const char *name, const char *routes) -{ - int index = get_interface_index(name); - if (index < 0) { - return index; - } - - rtentry rt4; - memset(&rt4, 0, sizeof(rt4)); - rt4.rt_dev = (char *)name; - rt4.rt_flags = RTF_UP; - rt4.rt_dst.sa_family = AF_INET; - rt4.rt_genmask.sa_family = AF_INET; - - in6_rtmsg rt6; - memset(&rt6, 0, sizeof(rt6)); - rt6.rtmsg_ifindex = index; - rt6.rtmsg_flags = RTF_UP; - - char address[65]; - int prefix; - int chars; - int count = 0; - - while (sscanf(routes, " %64[^/]/%d %n", address, &prefix, &chars) == 2) { - routes += chars; - - if (strchr(address, ':')) { - // Add an IPv6 route. - if (inet_pton(AF_INET6, address, &rt6.rtmsg_dst) != 1 || - prefix < 0 || prefix > 128) { - count = BAD_ARGUMENT; - break; - } - - rt6.rtmsg_dst_len = prefix ? prefix : 1; - if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) { - count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; - break; - } - - if (!prefix) { - // Split the route instead of replacing the default route. - rt6.rtmsg_dst.s6_addr[0] ^= 0x80; - if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) { - count = SYSTEM_ERROR; - break; - } - } - } else { - // Add an IPv4 route. - if (inet_pton(AF_INET, address, as_in_addr(&rt4.rt_dst)) != 1 || - prefix < 0 || prefix > 32) { - count = BAD_ARGUMENT; - break; - } - - in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0x80000000; - *as_in_addr(&rt4.rt_genmask) = htonl(mask); - if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) { - count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; - break; - } - - if (!prefix) { - // Split the route instead of replacing the default route. - *as_in_addr(&rt4.rt_dst) ^= htonl(0x80000000); - if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) { - count = SYSTEM_ERROR; - break; - } - } - } - ALOGD("Route added on %s: %s/%d", name, address, prefix); - ++count; - } - - if (count == BAD_ARGUMENT) { - ALOGE("Invalid route: %s/%d", address, prefix); - } else if (count == SYSTEM_ERROR) { - ALOGE("Cannot add route: %s/%d: %s", - address, prefix, strerror(errno)); - } else if (*routes) { - ALOGE("Invalid route: %s", routes); - count = BAD_ARGUMENT; - } - - return count; -} - static int reset_interface(const char *name) { ifreq ifr4; @@ -366,39 +276,6 @@ error: return count; } -static jint setRoutes(JNIEnv *env, jobject thiz, jstring jName, - jstring jRoutes) -{ - const char *name = NULL; - const char *routes = NULL; - int count = -1; - - name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; - if (!name) { - jniThrowNullPointerException(env, "name"); - goto error; - } - routes = jRoutes ? env->GetStringUTFChars(jRoutes, NULL) : NULL; - if (!routes) { - jniThrowNullPointerException(env, "routes"); - goto error; - } - count = set_routes(name, routes); - if (count < 0) { - throwException(env, count, "Cannot set route"); - count = -1; - } - -error: - if (name) { - env->ReleaseStringUTFChars(jName, name); - } - if (routes) { - env->ReleaseStringUTFChars(jRoutes, routes); - } - return count; -} - static void reset(JNIEnv *env, jobject thiz, jstring jName) { const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; @@ -430,7 +307,6 @@ static JNINativeMethod gMethods[] = { {"jniCreate", "(I)I", (void *)create}, {"jniGetName", "(I)Ljava/lang/String;", (void *)getName}, {"jniSetAddresses", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setAddresses}, - {"jniSetRoutes", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setRoutes}, {"jniReset", "(Ljava/lang/String;)V", (void *)reset}, {"jniCheck", "(Ljava/lang/String;)I", (void *)check}, }; |