am 673eb677: am 15884007: Merge "464xlat: use a gatewayed route, not point-to-point"

* commit '673eb677a27b6d761bd358edfc46ed64eedbb167':
  464xlat: use a gatewayed route, not point-to-point
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 697bde9..1dbe34e 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -355,11 +355,17 @@
      */
     public static final int TYPE_WIFI_P2P    = 13;
 
-    /** {@hide} */
-    public static final int MAX_RADIO_TYPE   = TYPE_WIFI_P2P;
+    /**
+     * The network to use for initially attaching to the network
+     * {@hide}
+     */
+    public static final int TYPE_MOBILE_IA = 14;
 
     /** {@hide} */
-    public static final int MAX_NETWORK_TYPE = TYPE_WIFI_P2P;
+    public static final int MAX_RADIO_TYPE   = TYPE_MOBILE_IA;
+
+    /** {@hide} */
+    public static final int MAX_NETWORK_TYPE = TYPE_MOBILE_IA;
 
     /**
      * If you want to set the default network preference,you can directly
@@ -436,6 +442,8 @@
                 return "MOBILE_CBS";
             case TYPE_WIFI_P2P:
                 return "WIFI_P2P";
+            case TYPE_MOBILE_IA:
+                return "MOBILE_IA";
             default:
                 return Integer.toString(type);
         }
@@ -458,6 +466,39 @@
             case TYPE_MOBILE_FOTA:
             case TYPE_MOBILE_IMS:
             case TYPE_MOBILE_CBS:
+            case TYPE_MOBILE_IA:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Checks if the given network type is backed by a Wi-Fi radio.
+     *
+     * @hide
+     */
+    public static boolean isNetworkTypeWifi(int networkType) {
+        switch (networkType) {
+            case TYPE_WIFI:
+            case TYPE_WIFI_P2P:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Checks if the given network type should be exempt from VPN routing rules
+     *
+     * @hide
+     */
+    public static boolean isNetworkTypeExempt(int networkType) {
+        switch (networkType) {
+            case TYPE_MOBILE_MMS:
+            case TYPE_MOBILE_SUPL:
+            case TYPE_MOBILE_HIPRI:
+            case TYPE_MOBILE_IA:
                 return true;
             default:
                 return false;
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 4600c1a..b0f7fc6 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -37,6 +37,9 @@
 /** {@hide} */
 interface IConnectivityManager
 {
+    // Keep this in sync with framework/native/services/connectivitymanager/ConnectivityManager.h
+    void markSocketAsUser(in ParcelFileDescriptor socket, int uid);
+
     void setNetworkPreference(int pref);
 
     int getNetworkPreference();
@@ -121,6 +124,8 @@
 
     ParcelFileDescriptor establishVpn(in VpnConfig config);
 
+    VpnConfig getVpnConfig();
+
     void startLegacyVpn(in VpnProfile profile);
 
     LegacyVpnInfo getLegacyVpnInfo();
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 4ab479e..b24d396 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -21,6 +21,7 @@
 import java.net.Inet6Address;
 import java.net.UnknownHostException;
 import java.util.Collection;
+import java.util.Locale;
 
 import android.util.Log;
 
@@ -103,6 +104,11 @@
     public native static String getDhcpError();
 
     /**
+     * Set the SO_MARK of {@code socketfd} to {@code mark}
+     */
+    public native static void markSocket(int socketfd, int mark);
+
+    /**
      * Convert a IPv4 address from an integer to an InetAddress.
      * @param hostAddress an int corresponding to the IPv4 address in network byte order
      */
@@ -223,7 +229,7 @@
     public static InetAddress hexToInet6Address(String addrHexString)
             throws IllegalArgumentException {
         try {
-            return numericToInetAddress(String.format("%s:%s:%s:%s:%s:%s:%s:%s",
+            return numericToInetAddress(String.format(Locale.US, "%s:%s:%s:%s:%s:%s:%s:%s",
                     addrHexString.substring(0,4),   addrHexString.substring(4,8),
                     addrHexString.substring(8,12),  addrHexString.substring(12,16),
                     addrHexString.substring(16,20), addrHexString.substring(20,24),
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index faae11e..526159f 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "NetUtils"
 
 #include "jni.h"
+#include "JNIHelp.h"
 #include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
@@ -239,6 +240,13 @@
     return env->NewStringUTF(::dhcp_get_errmsg());
 }
 
+static void android_net_utils_markSocket(JNIEnv *env, jobject thiz, jint socket, jint mark)
+{
+    if (setsockopt(socket, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
+        jniThrowException(env, "java/lang/IllegalStateException", "Error marking socket");
+    }
+}
+
 // ----------------------------------------------------------------------------
 
 /*
@@ -255,6 +263,7 @@
     { "stopDhcp", "(Ljava/lang/String;)Z",  (void *)android_net_utils_stopDhcp },
     { "releaseDhcpLease", "(Ljava/lang/String;)Z",  (void *)android_net_utils_releaseDhcpLease },
     { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
+    { "markSocket", "(II)V", (void*) android_net_utils_markSocket },
 };
 
 int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index cb4e89c..9615ff5 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -97,6 +97,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.Xml;
 
@@ -110,6 +111,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.connectivity.DataConnectionStats;
 import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
@@ -118,6 +120,8 @@
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
 
+import com.android.internal.annotations.GuardedBy;
+
 import dalvik.system.DexClassLoader;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -178,7 +182,8 @@
 
     private KeyStore mKeyStore;
 
-    private Vpn mVpn;
+    @GuardedBy("mVpns")
+    private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
     private VpnCallback mVpnCallback = new VpnCallback();
 
     private boolean mLockdownEnabled;
@@ -230,7 +235,6 @@
 
     private Object mDnsLock = new Object();
     private int mNumDnsEntries;
-    private boolean mDnsOverridden = false;
 
     private boolean mTestMode;
     private static ConnectivityService sServiceInstance;
@@ -247,6 +251,9 @@
     private static final boolean TO_DEFAULT_TABLE = true;
     private static final boolean TO_SECONDARY_TABLE = false;
 
+    private static final boolean EXEMPT = true;
+    private static final boolean UNEXEMPT = false;
+
     /**
      * used internally as a delayed event to make us switch back to the
      * default network
@@ -302,28 +309,22 @@
     private static final int EVENT_SET_DEPENDENCY_MET = 10;
 
     /**
-     * used internally to restore DNS properties back to the
-     * default network
-     */
-    private static final int EVENT_RESTORE_DNS = 11;
-
-    /**
      * used internally to send a sticky broadcast delayed.
      */
-    private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 12;
+    private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 11;
 
     /**
      * Used internally to
      * {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
      */
-    private static final int EVENT_SET_POLICY_DATA_ENABLE = 13;
+    private static final int EVENT_SET_POLICY_DATA_ENABLE = 12;
 
-    private static final int EVENT_VPN_STATE_CHANGED = 14;
+    private static final int EVENT_VPN_STATE_CHANGED = 13;
 
     /**
      * Used internally to disable fail fast of mobile data
      */
-    private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 15;
+    private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14;
 
     /** Handler used for internal events. */
     private InternalHandler mHandler;
@@ -344,10 +345,19 @@
 
     private InetAddress mDefaultDns;
 
+    // Lock for protecting access to mAddedRoutes and mExemptAddresses
+    private final Object mRoutesLock = new Object();
+
     // this collection is used to refcount the added routes - if there are none left
     // it's time to remove the route from the route table
+    @GuardedBy("mRoutesLock")
     private Collection<RouteInfo> mAddedRoutes = new ArrayList<RouteInfo>();
 
+    // this collection corresponds to the entries of mAddedRoutes that have routing exemptions
+    // used to handle cleanup of exempt rules
+    @GuardedBy("mRoutesLock")
+    private Collection<LinkAddress> mExemptAddresses = new ArrayList<LinkAddress>();
+
     // used in DBG mode to track inet condition reports
     private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
     private ArrayList mInetLog;
@@ -379,6 +389,7 @@
     // the set of network types that can only be enabled by system/sig apps
     List mProtectedNetworks;
 
+    private DataConnectionStats mDataConnectionStats;
     private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
 
     TelephonyManager mTelephonyManager;
@@ -461,6 +472,7 @@
                 com.android.internal.R.array.radioAttributes);
         for (String raString : raStrings) {
             RadioAttributes r = new RadioAttributes(raString);
+            if (VDBG) log("raString=" + raString + " r=" + r);
             if (r.mType > ConnectivityManager.MAX_RADIO_TYPE) {
                 loge("Error in radioAttributes - ignoring attempt to define type " + r.mType);
                 continue;
@@ -481,6 +493,7 @@
         for (String naString : naStrings) {
             try {
                 NetworkConfig n = new NetworkConfig(naString);
+                if (VDBG) log("naString=" + naString + " config=" + n);
                 if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) {
                     loge("Error in networkAttributes - ignoring attempt to define type " +
                             n.type);
@@ -507,6 +520,7 @@
                 // ignore it - leave the entry null
             }
         }
+        if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
 
         mProtectedNetworks = new ArrayList<Integer>();
         int[] protectedNetworks = context.getResources().getIntArray(
@@ -589,9 +603,12 @@
 
         mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper());
 
-        mVpn = new Vpn(mContext, mVpnCallback, mNetd, this);
-        mVpn.startMonitoring(mContext, mTrackerHandler);
-
+        //set up the listener for user state for creating user VPNs
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_STARTING);
+        intentFilter.addAction(Intent.ACTION_USER_STOPPING);
+        mContext.registerReceiverAsUser(
+                mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
         mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler);
 
         try {
@@ -611,6 +628,9 @@
 
         mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this);
         loadGlobalProxy();
+
+        mDataConnectionStats = new DataConnectionStats(mContext);
+        mDataConnectionStats.startMonitoring();
     }
 
     /**
@@ -1458,7 +1478,7 @@
         try {
             InetAddress addr = InetAddress.getByAddress(hostAddress);
             LinkProperties lp = tracker.getLinkProperties();
-            boolean ok = addRouteToAddress(lp, addr);
+            boolean ok = addRouteToAddress(lp, addr, EXEMPT);
             if (DBG) log("requestRouteToHostAddress ok=" + ok);
             return ok;
         } catch (UnknownHostException e) {
@@ -1470,24 +1490,25 @@
         return false;
     }
 
-    private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
-        return modifyRoute(p, r, 0, ADD, toDefaultTable);
+    private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
+            boolean exempt) {
+        return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt);
     }
 
     private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
-        return modifyRoute(p, r, 0, REMOVE, toDefaultTable);
+        return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT);
     }
 
-    private boolean addRouteToAddress(LinkProperties lp, InetAddress addr) {
-        return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE);
+    private boolean addRouteToAddress(LinkProperties lp, InetAddress addr, boolean exempt) {
+        return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt);
     }
 
     private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) {
-        return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE);
+        return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE, UNEXEMPT);
     }
 
     private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
-            boolean toDefaultTable) {
+            boolean toDefaultTable, boolean exempt) {
         RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
         if (bestRoute == null) {
             bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
@@ -1502,11 +1523,11 @@
                 bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
             }
         }
-        return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable);
+        return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt);
     }
 
     private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
-            boolean toDefaultTable) {
+            boolean toDefaultTable, boolean exempt) {
         if ((lp == null) || (r == null)) {
             if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
             return false;
@@ -1535,15 +1556,25 @@
                                                         bestRoute.getGateway(),
                                                         ifaceName);
                 }
-                modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable);
+                modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt);
             }
         }
         if (doAdd) {
             if (VDBG) log("Adding " + r + " for interface " + ifaceName);
             try {
                 if (toDefaultTable) {
-                    mAddedRoutes.add(r);  // only track default table - only one apps can effect
-                    mNetd.addRoute(ifaceName, r);
+                    synchronized (mRoutesLock) {
+                        // only track default table - only one apps can effect
+                        mAddedRoutes.add(r);
+                        mNetd.addRoute(ifaceName, r);
+                        if (exempt) {
+                            LinkAddress dest = r.getDestination();
+                            if (!mExemptAddresses.contains(dest)) {
+                                mNetd.setHostExemption(dest);
+                                mExemptAddresses.add(dest);
+                            }
+                        }
+                    }
                 } else {
                     mNetd.addSecondaryRoute(ifaceName, r);
                 }
@@ -1556,18 +1587,25 @@
             // if we remove this one and there are no more like it, then refcount==0 and
             // we can remove it from the table
             if (toDefaultTable) {
-                mAddedRoutes.remove(r);
-                if (mAddedRoutes.contains(r) == false) {
-                    if (VDBG) log("Removing " + r + " for interface " + ifaceName);
-                    try {
-                        mNetd.removeRoute(ifaceName, r);
-                    } catch (Exception e) {
-                        // never crash - catch them all
-                        if (VDBG) loge("Exception trying to remove a route: " + e);
-                        return false;
+                synchronized (mRoutesLock) {
+                    mAddedRoutes.remove(r);
+                    if (mAddedRoutes.contains(r) == false) {
+                        if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+                        try {
+                            mNetd.removeRoute(ifaceName, r);
+                            LinkAddress dest = r.getDestination();
+                            if (mExemptAddresses.contains(dest)) {
+                                mNetd.clearHostExemption(dest);
+                                mExemptAddresses.remove(dest);
+                            }
+                        } catch (Exception e) {
+                            // never crash - catch them all
+                            if (VDBG) loge("Exception trying to remove a route: " + e);
+                            return false;
+                        }
+                    } else {
+                        if (VDBG) log("not removing " + r + " as it's still in use");
                     }
-                } else {
-                    if (VDBG) log("not removing " + r + " as it's still in use");
                 }
             } else {
                 if (VDBG) log("Removing " + r + " for interface " + ifaceName);
@@ -1744,6 +1782,16 @@
                 "ConnectivityService");
     }
 
+    private void enforceMarkNetworkSocketPermission() {
+        //Media server special case
+        if (Binder.getCallingUid() == Process.MEDIA_UID) {
+            return;
+        }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MARK_NETWORK_SOCKET,
+                "ConnectivityService");
+    }
+
     /**
      * Handle a {@code DISCONNECTED} event. If this pertains to the non-active
      * network, we ignore it. If it is for the active network, we send out a
@@ -2238,6 +2286,7 @@
      */
     private void handleConnectivityChange(int netType, boolean doReset) {
         int resetMask = doReset ? NetworkUtils.RESET_ALL_ADDRESSES : 0;
+        boolean exempt = ConnectivityManager.isNetworkTypeExempt(netType);
 
         /*
          * If a non-default network is enabled, add the host routes that
@@ -2302,7 +2351,7 @@
             }
         }
         mCurrentLinkProperties[netType] = newLp;
-        boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault());
+        boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault(), exempt);
 
         if (resetMask != 0 || resetDns) {
             if (curLp != null) {
@@ -2315,7 +2364,11 @@
                             // Tell VPN the interface is down. It is a temporary
                             // but effective fix to make VPN aware of the change.
                             if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) {
-                                mVpn.interfaceStatusChanged(iface, false);
+                                synchronized(mVpns) {
+                                    for (int i = 0; i < mVpns.size(); i++) {
+                                        mVpns.valueAt(i).interfaceStatusChanged(iface, false);
+                                    }
+                                }
                             }
                         }
                         if (resetDns) {
@@ -2374,7 +2427,7 @@
      * returns a boolean indicating the routes changed
      */
     private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp,
-            boolean isLinkDefault) {
+            boolean isLinkDefault, boolean exempt) {
         Collection<RouteInfo> routesToAdd = null;
         CompareResult<InetAddress> dnsDiff = new CompareResult<InetAddress>();
         CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
@@ -2410,7 +2463,7 @@
                 }
                 if (newLp != null) {
                     for (InetAddress newDns : newLp.getDnses()) {
-                        addRouteToAddress(newLp, newDns);
+                        addRouteToAddress(newLp, newDns, exempt);
                     }
                 }
             } else {
@@ -2419,28 +2472,30 @@
                     removeRouteToAddress(curLp, oldDns);
                 }
                 for (InetAddress newDns : dnsDiff.added) {
-                    addRouteToAddress(newLp, newDns);
+                    addRouteToAddress(newLp, newDns, exempt);
                 }
             }
         }
 
         for (RouteInfo r :  routeDiff.added) {
             if (isLinkDefault || ! r.isDefaultRoute()) {
-                addRoute(newLp, r, TO_DEFAULT_TABLE);
+                addRoute(newLp, r, TO_DEFAULT_TABLE, exempt);
             } else {
                 // add to a secondary route table
-                addRoute(newLp, r, TO_SECONDARY_TABLE);
+                addRoute(newLp, r, TO_SECONDARY_TABLE, UNEXEMPT);
 
                 // many radios add a default route even when we don't want one.
                 // remove the default route unless somebody else has asked for it
                 String ifaceName = newLp.getInterfaceName();
-                if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) {
-                    if (VDBG) log("Removing " + r + " for interface " + ifaceName);
-                    try {
-                        mNetd.removeRoute(ifaceName, r);
-                    } catch (Exception e) {
-                        // never crash - catch them all
-                        if (DBG) loge("Exception trying to remove a route: " + e);
+                synchronized (mRoutesLock) {
+                    if (!TextUtils.isEmpty(ifaceName) && !mAddedRoutes.contains(r)) {
+                        if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+                        try {
+                            mNetd.removeRoute(ifaceName, r);
+                        } catch (Exception e) {
+                            // never crash - catch them all
+                            if (DBG) loge("Exception trying to remove a route: " + e);
+                        }
                     }
                 }
             }
@@ -2560,7 +2615,7 @@
 
     // Caller must grab mDnsLock.
     private void updateDnsLocked(String network, String iface,
-            Collection<InetAddress> dnses, String domains) {
+            Collection<InetAddress> dnses, String domains, boolean defaultDns) {
         int last = 0;
         if (dnses.size() == 0 && mDefaultDns != null) {
             dnses = new ArrayList();
@@ -2572,7 +2627,10 @@
 
         try {
             mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains);
-            mNetd.setDefaultInterfaceForDns(iface);
+            if (defaultDns) {
+                mNetd.setDefaultInterfaceForDns(iface);
+            }
+
             for (InetAddress dns : dnses) {
                 ++last;
                 String key = "net.dns" + last;
@@ -2599,9 +2657,7 @@
             if (mNetConfigs[netType].isDefault()) {
                 String network = nt.getNetworkInfo().getTypeName();
                 synchronized (mDnsLock) {
-                    if (!mDnsOverridden) {
-                        updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains());
-                    }
+                    updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains(), true);
                 }
             } else {
                 try {
@@ -2854,13 +2910,6 @@
                     handleSetDependencyMet(msg.arg2, met);
                     break;
                 }
-                case EVENT_RESTORE_DNS:
-                {
-                    if (mActiveDefaultNetwork != -1) {
-                        handleDnsConfigurationChange(mActiveDefaultNetwork);
-                    }
-                    break;
-                }
                 case EVENT_SEND_STICKY_BROADCAST_INTENT:
                 {
                     Intent intent = (Intent)msg.obj;
@@ -3205,6 +3254,10 @@
                 Settings.Global.HTTP_PROXY);
         if (!TextUtils.isEmpty(proxy)) {
             String data[] = proxy.split(":");
+            if (data.length == 0) {
+                return;
+            }
+
             String proxyHost =  data[0];
             int proxyPort = 8080;
             if (data.length > 1) {
@@ -3216,6 +3269,8 @@
             }
             ProxyProperties p = new ProxyProperties(data[0], proxyPort, "");
             setGlobalProxy(p);
+        } else {
+            setGlobalProxy(null);
         }
     }
 
@@ -3315,8 +3370,12 @@
         throwIfLockdownEnabled();
         try {
             int type = mActiveDefaultNetwork;
+            int user = UserHandle.getUserId(Binder.getCallingUid());
             if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
-                mVpn.protect(socket, mNetTrackers[type].getLinkProperties().getInterfaceName());
+                synchronized(mVpns) {
+                    mVpns.get(user).protect(socket,
+                            mNetTrackers[type].getLinkProperties().getInterfaceName());
+                }
                 return true;
             }
         } catch (Exception e) {
@@ -3340,7 +3399,27 @@
     @Override
     public boolean prepareVpn(String oldPackage, String newPackage) {
         throwIfLockdownEnabled();
-        return mVpn.prepare(oldPackage, newPackage);
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            return mVpns.get(user).prepare(oldPackage, newPackage);
+        }
+    }
+
+    @Override
+    public void markSocketAsUser(ParcelFileDescriptor socket, int uid) {
+        enforceMarkNetworkSocketPermission();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            int mark = mNetd.getMarkForUid(uid);
+            // Clear the mark on the socket if no mark is needed to prevent socket reuse issues
+            if (mark == -1) {
+                mark = 0;
+            }
+            NetworkUtils.markSocket(socket.getFd(), mark);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     /**
@@ -3353,7 +3432,10 @@
     @Override
     public ParcelFileDescriptor establishVpn(VpnConfig config) {
         throwIfLockdownEnabled();
-        return mVpn.establish(config);
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            return mVpns.get(user).establish(config);
+        }
     }
 
     /**
@@ -3367,7 +3449,10 @@
         if (egress == null) {
             throw new IllegalStateException("Missing active network connection");
         }
-        mVpn.startLegacyVpn(profile, mKeyStore, egress);
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
+        }
     }
 
     /**
@@ -3379,7 +3464,24 @@
     @Override
     public LegacyVpnInfo getLegacyVpnInfo() {
         throwIfLockdownEnabled();
-        return mVpn.getLegacyVpnInfo();
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            return mVpns.get(user).getLegacyVpnInfo();
+        }
+    }
+
+    /**
+     * Returns the information of the ongoing VPN. This method is used by VpnDialogs and
+     * not available in ConnectivityManager.
+     * Permissions are checked in Vpn class.
+     * @hide
+     */
+    @Override
+    public VpnConfig getVpnConfig() {
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            return mVpns.get(user).getVpnConfig();
+        }
     }
 
     /**
@@ -3400,7 +3502,7 @@
             mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget();
         }
 
-        public void override(List<String> dnsServers, List<String> searchDomains) {
+        public void override(String iface, List<String> dnsServers, List<String> searchDomains) {
             if (dnsServers == null) {
                 restore();
                 return;
@@ -3432,8 +3534,7 @@
 
             // Apply DNS changes.
             synchronized (mDnsLock) {
-                updateDnsLocked("VPN", "VPN", addresses, domains);
-                mDnsOverridden = true;
+                updateDnsLocked("VPN", iface, addresses, domains, false);
             }
 
             // Temporarily disable the default proxy (not global).
@@ -3448,12 +3549,6 @@
         }
 
         public void restore() {
-            synchronized (mDnsLock) {
-                if (mDnsOverridden) {
-                    mDnsOverridden = false;
-                    mHandler.sendEmptyMessage(EVENT_RESTORE_DNS);
-                }
-            }
             synchronized (mProxyLock) {
                 mDefaultProxyDisabled = false;
                 if (mGlobalProxy == null && mDefaultProxy != null) {
@@ -3461,6 +3556,67 @@
                 }
             }
         }
+
+        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) {
+            int uidStart = uid * UserHandle.PER_USER_RANGE;
+            int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
+            addUidForwarding(interfaze, uidStart, uidEnd);
+        }
+
+        public void clearUserForwarding(String interfaze, int uid) {
+            int uidStart = uid * UserHandle.PER_USER_RANGE;
+            int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
+            clearUidForwarding(interfaze, uidStart, uidEnd);
+        }
+
+        public void addUidForwarding(String interfaze, int uidStart, int uidEnd) {
+            try {
+                mNetd.setUidRangeRoute(interfaze,uidStart, uidEnd);
+                mNetd.setDnsInterfaceForUidRange(interfaze, uidStart, uidEnd);
+            } catch (RemoteException e) {
+            }
+
+        }
+
+        public void clearUidForwarding(String interfaze, int uidStart, int uidEnd) {
+            try {
+                mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
+                mNetd.clearDnsInterfaceForUidRange(uidStart, uidEnd);
+            } catch (RemoteException e) {
+            }
+
+        }
     }
 
     @Override
@@ -3481,7 +3637,11 @@
             final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
             final VpnProfile profile = VpnProfile.decode(
                     profileName, mKeyStore.get(Credentials.VPN + profileName));
-            setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpn, profile));
+            int user = UserHandle.getUserId(Binder.getCallingUid());
+            synchronized(mVpns) {
+                setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user),
+                            profile));
+            }
         } else {
             setLockdownTracker(null);
         }
@@ -3562,7 +3722,7 @@
     }
 
     @Override
-    public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs,
+    public int checkMobileProvisioning(final boolean sendNotification, int suggestedTimeOutMs,
             final ResultReceiver resultReceiver) {
         log("checkMobileProvisioning: E sendNotification=" + sendNotification
                 + " suggestedTimeOutMs=" + suggestedTimeOutMs
@@ -3597,6 +3757,10 @@
                         log("CheckMp.onComplete: send result");
                         resultReceiver.send(result, null);
                     }
+                    if (!sendNotification) {
+                        log("CheckMp.onComplete: done, not sending notification");
+                        return;
+                    }
                     NetworkInfo ni =
                             mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo();
                     switch(result) {
@@ -4117,4 +4281,43 @@
 
         return url;
     }
+
+    private void onUserStart(int userId) {
+        synchronized(mVpns) {
+            Vpn userVpn = mVpns.get(userId);
+            if (userVpn != null) {
+                loge("Starting user already has a VPN");
+                return;
+            }
+            userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId);
+            mVpns.put(userId, userVpn);
+            userVpn.startMonitoring(mContext, mTrackerHandler);
+        }
+    }
+
+    private void onUserStop(int userId) {
+        synchronized(mVpns) {
+            Vpn userVpn = mVpns.get(userId);
+            if (userVpn == null) {
+                loge("Stopping user has no VPN");
+                return;
+            }
+            mVpns.delete(userId);
+        }
+    }
+
+    private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            if (userId == UserHandle.USER_NULL) return;
+
+            if (Intent.ACTION_USER_STARTING.equals(action)) {
+                onUserStart(userId);
+            } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
+                onUserStop(userId);
+            }
+        }
+    };
 }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index cdc4d78..aca77b8 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -878,8 +878,7 @@
         mAlarmManager.remove(isA(PendingIntent.class));
         expectLastCall().anyTimes();
 
-        mAlarmManager.setInexactRepeating(
-                eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), isA(PendingIntent.class));
+        mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(), isA(PendingIntent.class));
         expectLastCall().atLeastOnce();
 
         mNetManager.setGlobalAlert(anyLong());