summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/DnsProxyListener.cpp17
-rw-r--r--server/DnsProxyListener.h3
-rw-r--r--server/FwmarkServer.cpp59
-rw-r--r--server/Network.cpp4
-rw-r--r--server/Network.h1
-rw-r--r--server/NetworkController.cpp203
-rw-r--r--server/NetworkController.h16
-rw-r--r--server/PhysicalNetwork.cpp30
-rw-r--r--server/PhysicalNetwork.h13
-rw-r--r--server/RouteController.cpp42
-rw-r--r--server/RouteController.h5
11 files changed, 324 insertions, 69 deletions
diff --git a/server/DnsProxyListener.cpp b/server/DnsProxyListener.cpp
index c88e788a..6e270572 100644
--- a/server/DnsProxyListener.cpp
+++ b/server/DnsProxyListener.cpp
@@ -48,14 +48,6 @@ DnsProxyListener::DnsProxyListener(const NetworkController* netCtrl) :
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 @@ int DnsProxyListener::GetAddrInfoCmd::runCommand(SocketClient *cli,
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 @@ int DnsProxyListener::GetHostByNameCmd::runCommand(SocketClient *cli,
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 @@ int DnsProxyListener::GetHostByAddrCmd::runCommand(SocketClient *cli,
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 5862ac76..106961e3 100644
--- a/server/DnsProxyListener.h
+++ b/server/DnsProxyListener.h
@@ -124,9 +124,6 @@ private:
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 d8098e8c..8bf8b718 100644
--- a/server/FwmarkServer.cpp
+++ b/server/FwmarkServer.cpp
@@ -102,17 +102,45 @@ int FwmarkServer::processClient(SocketClient* client, int* socketFd) {
}
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 @@ int FwmarkServer::processClient(SocketClient* client, int* socketFd) {
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 @@ int FwmarkServer::processClient(SocketClient* client, int* socketFd) {
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 5104de2d..0ca62474 100644
--- a/server/Network.cpp
+++ b/server/Network.cpp
@@ -33,6 +33,10 @@ bool Network::hasInterface(const std::string& interface) const {
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 39c81aab..115997ad 100644
--- a/server/Network.h
+++ b/server/Network.h
@@ -40,6 +40,7 @@ public:
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 d1514901..2ea71e29 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 @@ const unsigned MAX_NET_ID = 65535;
} // 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 @@ int NetworkController::setDefaultNetwork(unsigned netId) {
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);
+ 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 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 && (!forDns || virtualNetwork->getHasDns())) {
+ if (virtualNetwork && !virtualNetwork->isSecure()) {
return virtualNetwork->getNetId();
}
- return getNetworkLocked(requestedNetId) ? requestedNetId : mDefaultNetId;
+ return mDefaultNetId;
}
unsigned NetworkController::getNetworkForInterface(const char* interface) const {
@@ -129,7 +252,7 @@ int NetworkController::createPhysicalNetwork(unsigned netId, Permission permissi
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 @@ int NetworkController::createVirtualNetwork(unsigned netId, bool hasDns, bool se
}
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 @@ int NetworkController::destroyNetwork(unsigned netId) {
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 @@ void NetworkController::setPermissionForUsers(Permission permission,
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 @@ Permission NetworkController::getPermissionForUserLocked(uid_t uid) const {
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 @@ int NetworkController::modifyRoute(unsigned netId, const char* interface, const
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 fbd31edc..fca4125e 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -47,11 +47,12 @@ public:
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 @@ private:
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 395bea42..62343c4b 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 @@ int PhysicalNetwork::setPermission(Permission permission) {
}
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 @@ int PhysicalNetwork::addAsDefault() {
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 @@ int PhysicalNetwork::removeAsDefault() {
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 @@ int PhysicalNetwork::addInterface(const std::string& interface) {
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 @@ int PhysicalNetwork::removeInterface(const std::string& interface) {
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 6ee118b5..2ef10dfe 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 @@ private:
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 4353174a..355326d4 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -46,7 +46,7 @@ const uint32_t RULE_PRIORITY_LOCAL_NETWORK = 17000;
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 @@ WARN_UNUSED_RESULT int modifyImplicitNetworkRule(unsigned netId, uint32_t table,
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::enableTethering(const char* inputInterface, const char* out
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 e6abcc27..b21bc779 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -77,6 +77,11 @@ public:
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