Merge "Prohibit address families by default unless a VPN explicitly allows them." into lmp-dev
diff --git a/server/DnsProxyListener.cpp b/server/DnsProxyListener.cpp
index c88e788..6e27057 100644
--- a/server/DnsProxyListener.cpp
+++ b/server/DnsProxyListener.cpp
@@ -48,14 +48,6 @@
     registerCmd(new GetHostByNameCmd(this));
 }
 
-uint32_t DnsProxyListener::calcMark(unsigned netId) const {
-    Fwmark fwmark;
-    fwmark.netId = netId;
-    fwmark.protectedFromVpn = true;
-    fwmark.permission = PERMISSION_SYSTEM;
-    return fwmark.intValue;
-}
-
 DnsProxyListener::GetAddrInfoHandler::GetAddrInfoHandler(SocketClient *c,
                                                          char* host,
                                                          char* service,
@@ -202,8 +194,7 @@
     unsigned netId = strtoul(argv[7], NULL, 10);
     uid_t uid = cli->getUid();
 
-    netId = mDnsProxyListener->mNetCtrl->getNetworkForUser(uid, netId, true);
-    uint32_t mark = mDnsProxyListener->calcMark(netId);
+    uint32_t mark = mDnsProxyListener->mNetCtrl->getNetworkForDns(&netId, uid);
 
     if (ai_flags != -1 || ai_family != -1 ||
         ai_socktype != -1 || ai_protocol != -1) {
@@ -271,8 +262,7 @@
         name = strdup(name);
     }
 
-    netId = mDnsProxyListener->mNetCtrl->getNetworkForUser(uid, netId, true);
-    uint32_t mark = mDnsProxyListener->calcMark(netId);
+    uint32_t mark = mDnsProxyListener->mNetCtrl->getNetworkForDns(&netId, uid);
 
     cli->incRef();
     DnsProxyListener::GetHostByNameHandler* handler =
@@ -387,8 +377,7 @@
         return -1;
     }
 
-    netId = mDnsProxyListener->mNetCtrl->getNetworkForUser(uid, netId, true);
-    uint32_t mark = mDnsProxyListener->calcMark(netId);
+    uint32_t mark = mDnsProxyListener->mNetCtrl->getNetworkForDns(&netId, uid);
 
     cli->incRef();
     DnsProxyListener::GetHostByAddrHandler* handler =
diff --git a/server/DnsProxyListener.h b/server/DnsProxyListener.h
index 5862ac7..106961e 100644
--- a/server/DnsProxyListener.h
+++ b/server/DnsProxyListener.h
@@ -124,9 +124,6 @@
         unsigned mNetId;
         uint32_t mMark;
     };
-
-    // Calculate the socket mark to use for a DNS resolution.
-    uint32_t calcMark(unsigned netId) const;
 };
 
 #endif
diff --git a/server/FwmarkServer.cpp b/server/FwmarkServer.cpp
index d8098e8..8bf8b71 100644
--- a/server/FwmarkServer.cpp
+++ b/server/FwmarkServer.cpp
@@ -102,17 +102,45 @@
         }
 
         case FwmarkCommand::ON_CONNECT: {
-            // Called before a socket connect() happens. Set the default network's NetId into the
-            // fwmark so that the socket routes consistently over that network. Do this even if the
-            // socket already has a NetId, so that calling connect() multiple times still works.
+            // Called before a socket connect() happens. Set an appropriate NetId into the fwmark so
+            // that the socket routes consistently over that network. Do this even if the socket
+            // already has a NetId, so that calling connect() multiple times still works.
             //
-            // But respect the existing NetId if it had been explicitly preferred, indicated by:
-            // + The explicit bit having been set.
-            // + Or, the NetId being that of a VPN, which indicates a proxy acting on behalf of a
-            //   user who is subject to the VPN. The explicit bit is not set so that it works even
-            //   if the VPN is a split tunnel, but it's an explicit network preference nonetheless.
-            if (!fwmark.explicitlySelected && !mNetworkController->isVirtualNetwork(fwmark.netId)) {
-                fwmark.netId = mNetworkController->getDefaultNetwork();
+            // But if the explicit bit was set, the existing NetId was explicitly preferred (and not
+            // a case of connect() being called multiple times). Don't reset the NetId in that case.
+            //
+            // An "appropriate" NetId is the NetId of a bypassable VPN that applies to the user, or
+            // failing that, the default network. We'll never set the NetId of a secure VPN here.
+            // See the comments in the implementation of getNetworkForConnect() for more details.
+            //
+            // If the protect bit is set, this could be either a system proxy (e.g.: the dns proxy
+            // or the download manager) acting on behalf of another user, or a VPN provider. If it's
+            // a proxy, we shouldn't reset the NetId. If it's a VPN provider, we should set the
+            // default network's NetId.
+            //
+            // There's no easy way to tell the difference between a proxy and a VPN app. We can't
+            // use PERMISSION_SYSTEM to identify the proxy because a VPN app may also have those
+            // permissions. So we use the following heuristic:
+            //
+            // If it's a proxy, but the existing NetId is not a VPN, that means the user (that the
+            // proxy is acting on behalf of) is not subject to a VPN, so the proxy must have picked
+            // the default network's NetId. So, it's okay to replace that with the current default
+            // network's NetId (which in all likelihood is the same).
+            //
+            // Conversely, if it's a VPN provider, the existing NetId cannot be a VPN. The only time
+            // we set a VPN's NetId into a socket without setting the explicit bit is here, in
+            // ON_CONNECT, but we won't do that if the socket has the protect bit set. If the VPN
+            // provider connect()ed (and got the VPN NetId set) and then called protect(), we
+            // would've unset the NetId in PROTECT_FROM_VPN below.
+            //
+            // So, overall (when the explicit bit is not set but the protect bit is set), if the
+            // existing NetId is a VPN, don't reset it. Else, set the default network's NetId.
+            if (!fwmark.explicitlySelected) {
+                if (!fwmark.protectedFromVpn) {
+                    fwmark.netId = mNetworkController->getNetworkForConnect(client->getUid());
+                } else if (!mNetworkController->isVirtualNetwork(fwmark.netId)) {
+                    fwmark.netId = mNetworkController->getDefaultNetwork();
+                }
             }
             break;
         }
@@ -136,6 +164,15 @@
             if (!mNetworkController->canProtect(client->getUid())) {
                 return -EPERM;
             }
+            // If a bypassable VPN's provider app calls connect() and then protect(), it will end up
+            // with a socket that looks like that of a system proxy but is not (see comments for
+            // ON_CONNECT above). So, reset the NetId.
+            //
+            // In any case, it's appropriate that if the socket has an implicit VPN NetId mark, the
+            // PROTECT_FROM_VPN command should unset it.
+            if (!fwmark.explicitlySelected && mNetworkController->isVirtualNetwork(fwmark.netId)) {
+                fwmark.netId = mNetworkController->getDefaultNetwork();
+            }
             fwmark.protectedFromVpn = true;
             permission = static_cast<Permission>(permission | fwmark.permission);
             break;
@@ -145,7 +182,7 @@
             if ((permission & PERMISSION_SYSTEM) != PERMISSION_SYSTEM) {
                 return -EPERM;
             }
-            fwmark.netId = mNetworkController->getNetworkForUser(command.uid, NETID_UNSET, false);
+            fwmark.netId = mNetworkController->getNetworkForUser(command.uid);
             fwmark.protectedFromVpn = true;
             break;
         }
diff --git a/server/Network.cpp b/server/Network.cpp
index 5104de2..0ca6247 100644
--- a/server/Network.cpp
+++ b/server/Network.cpp
@@ -33,6 +33,10 @@
     return mInterfaces.find(interface) != mInterfaces.end();
 }
 
+const std::set<std::string>& Network::getInterfaces() const {
+    return mInterfaces;
+}
+
 int Network::clearInterfaces() {
     while (!mInterfaces.empty()) {
         // Make a copy of the string, so removeInterface() doesn't lose its parameter when it
diff --git a/server/Network.h b/server/Network.h
index 39c81aa..115997a 100644
--- a/server/Network.h
+++ b/server/Network.h
@@ -40,6 +40,7 @@
     unsigned getNetId() const;
 
     bool hasInterface(const std::string& interface) const;
+    const std::set<std::string>& getInterfaces() const;
 
     // These return 0 on success or negative errno on failure.
     virtual int addInterface(const std::string& interface) WARN_UNUSED_RESULT = 0;
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index d151490..2ea71e2 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -32,6 +32,7 @@
 
 #include "NetworkController.h"
 
+#include "Fwmark.h"
 #include "LocalNetwork.h"
 #include "PhysicalNetwork.h"
 #include "RouteController.h"
@@ -50,7 +51,81 @@
 
 }  // namespace
 
-NetworkController::NetworkController() : mDefaultNetId(NETID_UNSET) {
+// All calls to methods here are made while holding a write lock on mRWLock.
+class NetworkController::DelegateImpl : public PhysicalNetwork::Delegate {
+public:
+    explicit DelegateImpl(NetworkController* networkController);
+    virtual ~DelegateImpl();
+
+    int modifyFallthrough(unsigned vpnNetId, const std::string& physicalInterface,
+                          Permission permission, bool add) WARN_UNUSED_RESULT;
+
+private:
+    int addFallthrough(const std::string& physicalInterface,
+                       Permission permission) override WARN_UNUSED_RESULT;
+    int removeFallthrough(const std::string& physicalInterface,
+                          Permission permission) override WARN_UNUSED_RESULT;
+
+    int modifyFallthrough(const std::string& physicalInterface, Permission permission,
+                          bool add) WARN_UNUSED_RESULT;
+
+    NetworkController* const mNetworkController;
+};
+
+NetworkController::DelegateImpl::DelegateImpl(NetworkController* networkController) :
+        mNetworkController(networkController) {
+}
+
+NetworkController::DelegateImpl::~DelegateImpl() {
+}
+
+int NetworkController::DelegateImpl::modifyFallthrough(unsigned vpnNetId,
+                                                       const std::string& physicalInterface,
+                                                       Permission permission, bool add) {
+    if (add) {
+        if (int ret = RouteController::addVirtualNetworkFallthrough(vpnNetId,
+                                                                    physicalInterface.c_str(),
+                                                                    permission)) {
+            ALOGE("failed to add fallthrough to %s for VPN netId %u", physicalInterface.c_str(),
+                  vpnNetId);
+            return ret;
+        }
+    } else {
+        if (int ret = RouteController::removeVirtualNetworkFallthrough(vpnNetId,
+                                                                       physicalInterface.c_str(),
+                                                                       permission)) {
+            ALOGE("failed to remove fallthrough to %s for VPN netId %u", physicalInterface.c_str(),
+                  vpnNetId);
+            return ret;
+        }
+    }
+    return 0;
+}
+
+int NetworkController::DelegateImpl::addFallthrough(const std::string& physicalInterface,
+                                                    Permission permission) {
+    return modifyFallthrough(physicalInterface, permission, true);
+}
+
+int NetworkController::DelegateImpl::removeFallthrough(const std::string& physicalInterface,
+                                                       Permission permission) {
+    return modifyFallthrough(physicalInterface, permission, false);
+}
+
+int NetworkController::DelegateImpl::modifyFallthrough(const std::string& physicalInterface,
+                                                       Permission permission, bool add) {
+    for (const auto& entry : mNetworkController->mNetworks) {
+        if (entry.second->getType() == Network::VIRTUAL) {
+            if (int ret = modifyFallthrough(entry.first, physicalInterface, permission, add)) {
+                return ret;
+            }
+        }
+    }
+    return 0;
+}
+
+NetworkController::NetworkController() :
+        mDelegateImpl(new NetworkController::DelegateImpl(this)), mDefaultNetId(NETID_UNSET) {
     mNetworks[LOCAL_NET_ID] = new LocalNetwork(LOCAL_NET_ID);
 }
 
@@ -92,14 +167,62 @@
     return 0;
 }
 
-unsigned NetworkController::getNetworkForUser(uid_t uid, unsigned requestedNetId,
-                                              bool forDns) const {
+uint32_t NetworkController::getNetworkForDns(unsigned* netId, uid_t uid) const {
     android::RWLock::AutoRLock lock(mRWLock);
-    VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
-    if (virtualNetwork && (!forDns || virtualNetwork->getHasDns())) {
+    Fwmark fwmark;
+    fwmark.protectedFromVpn = true;
+    fwmark.permission = PERMISSION_SYSTEM;
+    if (canUserSelectNetworkLocked(uid, *netId)) {
+        // If a non-zero NetId was explicitly specified, and the user has permission for that
+        // network, use that network's DNS servers. Do not fall through to the default network even
+        // if the explicitly selected network is a split tunnel VPN or a VPN without DNS servers.
+        fwmark.explicitlySelected = true;
+    } else {
+        // If the user is subject to a VPN and the VPN provides DNS servers, use those servers
+        // (possibly falling through to the default network if the VPN doesn't provide a route to
+        // them). Otherwise, use the default network's DNS servers.
+        VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
+        if (virtualNetwork && virtualNetwork->getHasDns()) {
+            *netId = virtualNetwork->getNetId();
+        } else {
+            *netId = mDefaultNetId;
+        }
+    }
+    fwmark.netId = *netId;
+    return fwmark.intValue;
+}
+
+// Returns the NetId that a given UID would use if no network is explicitly selected. Specifically,
+// the VPN that applies to the UID if any; otherwise, the default network.
+unsigned NetworkController::getNetworkForUser(uid_t uid) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    if (VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid)) {
         return virtualNetwork->getNetId();
     }
-    return getNetworkLocked(requestedNetId) ? requestedNetId : mDefaultNetId;
+    return mDefaultNetId;
+}
+
+// Returns the NetId that will be set when a socket connect()s. This is the bypassable VPN that
+// applies to the user if any; otherwise, the default network.
+//
+// In general, we prefer to always set the default network's NetId in connect(), so that if the VPN
+// is a split-tunnel and disappears later, the socket continues working (since the default network's
+// NetId is still valid). Secure VPNs will correctly grab the socket's traffic since they have a
+// high-priority routing rule that doesn't care what NetId the socket has.
+//
+// But bypassable VPNs have a very low priority rule, so we need to mark the socket with the
+// bypassable VPN's NetId if we expect it to get any traffic at all. If the bypassable VPN is a
+// split-tunnel, that's okay, because we have fallthrough rules that will direct the fallthrough
+// traffic to the default network. But it does mean that if the bypassable VPN goes away (and thus
+// the fallthrough rules also go away), the socket that used to fallthrough to the default network
+// will stop working.
+unsigned NetworkController::getNetworkForConnect(uid_t uid) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
+    if (virtualNetwork && !virtualNetwork->isSecure()) {
+        return virtualNetwork->getNetId();
+    }
+    return mDefaultNetId;
 }
 
 unsigned NetworkController::getNetworkForInterface(const char* interface) const {
@@ -129,7 +252,7 @@
         return -EEXIST;
     }
 
-    PhysicalNetwork* physicalNetwork = new PhysicalNetwork(netId);
+    PhysicalNetwork* physicalNetwork = new PhysicalNetwork(netId, mDelegateImpl);
     if (int ret = physicalNetwork->setPermission(permission)) {
         ALOGE("inconceivable! setPermission cannot fail on an empty network");
         delete physicalNetwork;
@@ -153,6 +276,9 @@
     }
 
     android::RWLock::AutoWLock lock(mRWLock);
+    if (int ret = modifyFallthroughLocked(netId, true)) {
+        return ret;
+    }
     mNetworks[netId] = new VirtualNetwork(netId, hasDns, secure);
     return 0;
 }
@@ -176,6 +302,10 @@
             return ret;
         }
         mDefaultNetId = NETID_UNSET;
+    } else if (network->getType() == Network::VIRTUAL) {
+        if (int ret = modifyFallthroughLocked(netId, false)) {
+            return ret;
+        }
     }
     mNetworks.erase(netId);
     delete network;
@@ -224,24 +354,7 @@
 
 bool NetworkController::canUserSelectNetwork(uid_t uid, unsigned netId) const {
     android::RWLock::AutoRLock lock(mRWLock);
-    Network* network = getNetworkLocked(netId);
-    if (!network || uid == INVALID_UID) {
-        return false;
-    }
-    Permission userPermission = getPermissionForUserLocked(uid);
-    if ((userPermission & PERMISSION_SYSTEM) == PERMISSION_SYSTEM) {
-        return true;
-    }
-    if (network->getType() == Network::VIRTUAL) {
-        return static_cast<VirtualNetwork*>(network)->appliesToUser(uid);
-    }
-    VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
-    if (virtualNetwork && virtualNetwork->isSecure() &&
-            mProtectableUsers.find(uid) == mProtectableUsers.end()) {
-        return false;
-    }
-    Permission networkPermission = static_cast<PhysicalNetwork*>(network)->getPermission();
-    return (userPermission & networkPermission) == networkPermission;
+    return canUserSelectNetworkLocked(uid, netId);
 }
 
 int NetworkController::setPermissionForNetworks(Permission permission,
@@ -347,6 +460,29 @@
     return uid < FIRST_APPLICATION_UID ? PERMISSION_SYSTEM : PERMISSION_NONE;
 }
 
+bool NetworkController::canUserSelectNetworkLocked(uid_t uid, unsigned netId) const {
+    Network* network = getNetworkLocked(netId);
+    // If uid is INVALID_UID, this likely means that we were unable to retrieve the UID of the peer
+    // (using SO_PEERCRED). Be safe and deny access to the network, even if it's valid.
+    if (!network || uid == INVALID_UID) {
+        return false;
+    }
+    Permission userPermission = getPermissionForUserLocked(uid);
+    if ((userPermission & PERMISSION_SYSTEM) == PERMISSION_SYSTEM) {
+        return true;
+    }
+    if (network->getType() == Network::VIRTUAL) {
+        return static_cast<VirtualNetwork*>(network)->appliesToUser(uid);
+    }
+    VirtualNetwork* virtualNetwork = getVirtualNetworkForUserLocked(uid);
+    if (virtualNetwork && virtualNetwork->isSecure() &&
+            mProtectableUsers.find(uid) == mProtectableUsers.end()) {
+        return false;
+    }
+    Permission networkPermission = static_cast<PhysicalNetwork*>(network)->getPermission();
+    return (userPermission & networkPermission) == networkPermission;
+}
+
 int NetworkController::modifyRoute(unsigned netId, const char* interface, const char* destination,
                                    const char* nexthop, bool add, bool legacy, uid_t uid) {
     unsigned existingNetId = getNetworkForInterface(interface);
@@ -371,3 +507,22 @@
     return add ? RouteController::addRoute(interface, destination, nexthop, tableType) :
                  RouteController::removeRoute(interface, destination, nexthop, tableType);
 }
+
+int NetworkController::modifyFallthroughLocked(unsigned vpnNetId, bool add) {
+    if (mDefaultNetId == NETID_UNSET) {
+        return 0;
+    }
+    Network* network = getNetworkLocked(mDefaultNetId);
+    if (!network || network->getType() != Network::PHYSICAL) {
+        ALOGE("cannot find previously set default network with netId %u", mDefaultNetId);
+        return -ESRCH;
+    }
+    Permission permission = static_cast<PhysicalNetwork*>(network)->getPermission();
+    for (const auto& physicalInterface : network->getInterfaces()) {
+        if (int ret = mDelegateImpl->modifyFallthrough(vpnNetId, physicalInterface, permission,
+                                                       add)) {
+            return ret;
+        }
+    }
+    return 0;
+}
diff --git a/server/NetworkController.h b/server/NetworkController.h
index fbd31ed..fca4125 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -47,11 +47,12 @@
     unsigned getDefaultNetwork() const;
     int setDefaultNetwork(unsigned netId) WARN_UNUSED_RESULT;
 
-    // Order of preference: UID-specific, requestedNetId, default.
-    // Specify NETID_UNSET for requestedNetId if the default network is preferred.
-    // forDns indicates if we're querying the netId for a DNS request. This avoids sending DNS
-    // requests to VPNs without DNS servers.
-    unsigned getNetworkForUser(uid_t uid, unsigned requestedNetId, bool forDns) const;
+    // Sets |*netId| to an appropriate NetId to use for DNS for the given user. Call with |*netId|
+    // set to a non-NETID_UNSET value if the user already has indicated a preference. Returns the
+    // fwmark value to set on the socket when performing the DNS request.
+    uint32_t getNetworkForDns(unsigned* netId, uid_t uid) const;
+    unsigned getNetworkForUser(uid_t uid) const;
+    unsigned getNetworkForConnect(uid_t uid) const;
     unsigned getNetworkForInterface(const char* interface) const;
     bool isVirtualNetwork(unsigned netId) const;
 
@@ -90,9 +91,14 @@
     Network* getNetworkLocked(unsigned netId) const;
     VirtualNetwork* getVirtualNetworkForUserLocked(uid_t uid) const;
     Permission getPermissionForUserLocked(uid_t uid) const;
+    bool canUserSelectNetworkLocked(uid_t uid, unsigned netId) const;
 
     int modifyRoute(unsigned netId, const char* interface, const char* destination,
                     const char* nexthop, bool add, bool legacy, uid_t uid) WARN_UNUSED_RESULT;
+    int modifyFallthroughLocked(unsigned vpnNetId, bool add) WARN_UNUSED_RESULT;
+
+    class DelegateImpl;
+    DelegateImpl* const mDelegateImpl;
 
     // mRWLock guards all accesses to mDefaultNetId, mNetworks, mUsers and mProtectableUsers.
     mutable android::RWLock mRWLock;
diff --git a/server/PhysicalNetwork.cpp b/server/PhysicalNetwork.cpp
index 395bea4..62343c4 100644
--- a/server/PhysicalNetwork.cpp
+++ b/server/PhysicalNetwork.cpp
@@ -24,28 +24,38 @@
 namespace {
 
 WARN_UNUSED_RESULT int addToDefault(unsigned netId, const std::string& interface,
-                                    Permission permission) {
+                                    Permission permission, PhysicalNetwork::Delegate* delegate) {
     if (int ret = RouteController::addInterfaceToDefaultNetwork(interface.c_str(), permission)) {
         ALOGE("failed to add interface %s to default netId %u", interface.c_str(), netId);
         return ret;
     }
+    if (int ret = delegate->addFallthrough(interface, permission)) {
+        return ret;
+    }
     return 0;
 }
 
 WARN_UNUSED_RESULT int removeFromDefault(unsigned netId, const std::string& interface,
-                                         Permission permission) {
+                                         Permission permission,
+                                         PhysicalNetwork::Delegate* delegate) {
     if (int ret = RouteController::removeInterfaceFromDefaultNetwork(interface.c_str(),
                                                                      permission)) {
         ALOGE("failed to remove interface %s from default netId %u", interface.c_str(), netId);
         return ret;
     }
+    if (int ret = delegate->removeFallthrough(interface, permission)) {
+        return ret;
+    }
     return 0;
 }
 
 }  // namespace
 
-PhysicalNetwork::PhysicalNetwork(unsigned netId) :
-        Network(netId), mPermission(PERMISSION_NONE), mIsDefault(false) {
+PhysicalNetwork::Delegate::~Delegate() {
+}
+
+PhysicalNetwork::PhysicalNetwork(unsigned netId, PhysicalNetwork::Delegate* delegate) :
+        Network(netId), mDelegate(delegate), mPermission(PERMISSION_NONE), mIsDefault(false) {
 }
 
 PhysicalNetwork::~PhysicalNetwork() {
@@ -69,10 +79,10 @@
     }
     if (mIsDefault) {
         for (const std::string& interface : mInterfaces) {
-            if (int ret = addToDefault(mNetId, interface, permission)) {
+            if (int ret = addToDefault(mNetId, interface, permission, mDelegate)) {
                 return ret;
             }
-            if (int ret = removeFromDefault(mNetId, interface, mPermission)) {
+            if (int ret = removeFromDefault(mNetId, interface, mPermission, mDelegate)) {
                 return ret;
             }
         }
@@ -86,7 +96,7 @@
         return 0;
     }
     for (const std::string& interface : mInterfaces) {
-        if (int ret = addToDefault(mNetId, interface, mPermission)) {
+        if (int ret = addToDefault(mNetId, interface, mPermission, mDelegate)) {
             return ret;
         }
     }
@@ -99,7 +109,7 @@
         return 0;
     }
     for (const std::string& interface : mInterfaces) {
-        if (int ret = removeFromDefault(mNetId, interface, mPermission)) {
+        if (int ret = removeFromDefault(mNetId, interface, mPermission, mDelegate)) {
             return ret;
         }
     }
@@ -121,7 +131,7 @@
         return ret;
     }
     if (mIsDefault) {
-        if (int ret = addToDefault(mNetId, interface, mPermission)) {
+        if (int ret = addToDefault(mNetId, interface, mPermission, mDelegate)) {
             return ret;
         }
     }
@@ -139,7 +149,7 @@
         return ret;
     }
     if (mIsDefault) {
-        if (int ret = removeFromDefault(mNetId, interface, mPermission)) {
+        if (int ret = removeFromDefault(mNetId, interface, mPermission, mDelegate)) {
             return ret;
         }
     }
diff --git a/server/PhysicalNetwork.h b/server/PhysicalNetwork.h
index 6ee118b..2ef10df 100644
--- a/server/PhysicalNetwork.h
+++ b/server/PhysicalNetwork.h
@@ -22,7 +22,17 @@
 
 class PhysicalNetwork : public Network {
 public:
-    explicit PhysicalNetwork(unsigned netId);
+    class Delegate {
+    public:
+        virtual ~Delegate();
+
+        virtual int addFallthrough(const std::string& physicalInterface,
+                                   Permission permission) WARN_UNUSED_RESULT = 0;
+        virtual int removeFallthrough(const std::string& physicalInterface,
+                                      Permission permission) WARN_UNUSED_RESULT = 0;
+    };
+
+    PhysicalNetwork(unsigned netId, Delegate* delegate);
     virtual ~PhysicalNetwork();
 
     // These refer to permissions that apps must have in order to use this network.
@@ -37,6 +47,7 @@
     int addInterface(const std::string& interface) override WARN_UNUSED_RESULT;
     int removeInterface(const std::string& interface) override WARN_UNUSED_RESULT;
 
+    Delegate* const mDelegate;
     Permission mPermission;
     bool mIsDefault;
 };
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index 4353174..355326d 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -46,7 +46,7 @@
 const uint32_t RULE_PRIORITY_TETHERING           = 18000;
 const uint32_t RULE_PRIORITY_IMPLICIT_NETWORK    = 19000;
 const uint32_t RULE_PRIORITY_BYPASSABLE_VPN      = 20000;
-// const uint32_t RULE_PRIORITY_VPN_FALLTHROUGH     = 21000;
+const uint32_t RULE_PRIORITY_VPN_FALLTHROUGH     = 21000;
 const uint32_t RULE_PRIORITY_DEFAULT_NETWORK     = 22000;
 const uint32_t RULE_PRIORITY_DIRECTLY_CONNECTED  = 23000;
 const uint32_t RULE_PRIORITY_UNREACHABLE         = 24000;
@@ -565,6 +565,35 @@
                         fwmark.intValue, mask.intValue);
 }
 
+// A rule to enable split tunnel VPNs.
+//
+// If a packet with a VPN's netId doesn't find a route in the VPN's routing table, it's allowed to
+// go over the default network, provided it wasn't explicitly restricted to the VPN and has the
+// permissions required by the default network.
+WARN_UNUSED_RESULT int modifyVpnFallthroughRule(uint16_t action, unsigned vpnNetId,
+                                                const char* physicalInterface,
+                                                Permission permission) {
+    uint32_t table = getRouteTableForInterface(physicalInterface);
+    if (table == RT_TABLE_UNSPEC) {
+        return -ESRCH;
+    }
+
+    Fwmark fwmark;
+    Fwmark mask;
+
+    fwmark.netId = vpnNetId;
+    mask.netId = FWMARK_NET_ID_MASK;
+
+    fwmark.explicitlySelected = false;
+    mask.explicitlySelected = true;
+
+    fwmark.permission = permission;
+    mask.permission = permission;
+
+    return modifyIpRule(action, RULE_PRIORITY_VPN_FALLTHROUGH, table, fwmark.intValue,
+                        mask.intValue);
+}
+
 // Add rules to allow legacy routes added through the requestRouteToHost() API.
 WARN_UNUSED_RESULT int addLegacyRouteRules() {
     Fwmark fwmark;
@@ -949,3 +978,14 @@
 int RouteController::disableTethering(const char* inputInterface, const char* outputInterface) {
     return modifyTetheredNetwork(RTM_DELRULE, inputInterface, outputInterface);
 }
+
+int RouteController::addVirtualNetworkFallthrough(unsigned vpnNetId, const char* physicalInterface,
+                                                  Permission permission) {
+    return modifyVpnFallthroughRule(RTM_NEWRULE, vpnNetId, physicalInterface, permission);
+}
+
+int RouteController::removeVirtualNetworkFallthrough(unsigned vpnNetId,
+                                                     const char* physicalInterface,
+                                                     Permission permission) {
+    return modifyVpnFallthroughRule(RTM_DELRULE, vpnNetId, physicalInterface, permission);
+}
diff --git a/server/RouteController.h b/server/RouteController.h
index e6abcc2..b21bc77 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -77,6 +77,11 @@
                                const char* outputInterface) WARN_UNUSED_RESULT;
     static int disableTethering(const char* inputInterface,
                                 const char* outputInterface) WARN_UNUSED_RESULT;
+
+    static int addVirtualNetworkFallthrough(unsigned vpnNetId, const char* physicalInterface,
+                                            Permission permission) WARN_UNUSED_RESULT;
+    static int removeVirtualNetworkFallthrough(unsigned vpnNetId, const char* physicalInterface,
+                                               Permission permission) WARN_UNUSED_RESULT;
 };
 
 #endif  // NETD_SERVER_ROUTE_CONTROLLER_H