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
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 @@
     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 @@
     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 @@
 
     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 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 @@
     }
 
     /**
+     * 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 @@
      * 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 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 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 @@
                 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 @@
                 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 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.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 @@
      * @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.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 @@
     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 @@
     boolean isNetworkActive();
 
     /**
-     * Setup a new network.
+     * Setup a new physical network.
      */
-    void createNetwork(int netId);
+    void createPhysicalNetwork(int netId);
+
+    /**
+     * Setup a new VPN.
+     */
+    void createVirtualNetwork(int netId, boolean hasDNS);
 
     /**
      * Remove a network.
@@ -437,4 +429,14 @@
 
     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 @@
     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 @@
     { "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.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 @@
 
     @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 @@
      */
     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 @@
                     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 @@
                 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 @@
                     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 @@
     }
 
     /**
-     * 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 @@
         }
     }
 
-    /**
-     * 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 @@
                 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 @@
             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 @@
             }
         }
         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 @@
             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 @@
                 // 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 @@
             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.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.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 @@
     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 @@
     }
 
     @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();
+        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 clearUidRangeRoute(String iface, int uid_start, int uid_end) {
+    public void removeVpnUidRanges(int netId, UidRange[] ranges) {
         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();
-        }
-    }
-
-    @Override
-    public void clearMarkedForwarding(String iface) {
-        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 @@
     }
 
     @Override
-    public void createNetwork(int netId) {
+    public void createPhysicalNetwork(int netId) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
         try {
@@ -2026,6 +2032,17 @@
     }
 
     @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 @@
             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 @@
         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 @@
                 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.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.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.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.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 @@
 /**
  * @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 @@
     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 @@
             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 @@
         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 @@
 
         // 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 @@
             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 {
-
-        PackageManager pm = mContext.getPackageManager();
-        int appUid = pm.getPackageUid(mPackage, mUserId);
-        if (Binder.getCallingUid() != appUid) {
-            throw new SecurityException("Unauthorized Caller");
+    private int getAppUid(String app) {
+        if (app == VpnConfig.LEGACY_VPN) {
+            return Process.myUid();
         }
-        // protect the socket from routing rules
-        final long token = Binder.clearCallingIdentity();
+        PackageManager pm = mContext.getPackageManager();
+        int result;
         try {
-            mCallback.protect(socket);
+            result = pm.getPackageUid(app, mUserId);
+        } catch (NameNotFoundException e) {
+            result = -1;
+        }
+        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 {
+            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 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 @@
         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 @@
             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 @@
         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 @@
     }
 
     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 @@
             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 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 @@
 
     private void showNotification(String label, Bitmap icon, int user) {
         if (!mEnableNotif) return;
-        mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext);
 
-        NotificationManager nm = (NotificationManager)
-                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+            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);
+            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));
+                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 @@
                 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 @@
             for (LocalSocket socket : mSockets) {
                 IoUtils.closeQuietly(socket);
             }
-            updateState(DetailedState.DISCONNECTED, "exit");
+            agentDisconnect();
             try {
                 mContext.unregisterReceiver(mBroadcastReceiver);
             } catch (IllegalArgumentException e) {}
@@ -1018,7 +1050,7 @@
                     restart = restart || (arguments != null);
                 }
                 if (!restart) {
-                    updateState(DetailedState.DISCONNECTED, "execute");
+                    agentDisconnect();
                     return;
                 }
                 updateState(DetailedState.CONNECTING, "execute");
@@ -1129,15 +1161,6 @@
                     }
                 }
 
-                // 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 @@
 
                     // Now INetworkManagementEventObserver is watching our back.
                     mInterface = mConfig.interfaze;
-                    mVpnUsers = new SparseBooleanArray();
+                    mVpnUsers = new ArrayList<UidRange>();
 
-                    token = Binder.clearCallingIdentity();
-                    try {
-                        mCallback.override(mInterface, mConfig.dnsServers, mConfig.searchDomains);
-                        addVpnUserLocked(mUserId);
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
+                    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 @@
 
                 // Do not leave an unstable state.
                 if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
-                    updateState(DetailedState.FAILED, "execute");
+                    agentDisconnect();
                 }
             }
         }
@@ -1232,7 +1225,7 @@
                     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 @@
     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 @@
     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 @@
     {"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},
 };