diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/DnsProxyListener.cpp | 17 | ||||
-rw-r--r-- | server/DnsProxyListener.h | 3 | ||||
-rw-r--r-- | server/FwmarkServer.cpp | 59 | ||||
-rw-r--r-- | server/Network.cpp | 4 | ||||
-rw-r--r-- | server/Network.h | 1 | ||||
-rw-r--r-- | server/NetworkController.cpp | 203 | ||||
-rw-r--r-- | server/NetworkController.h | 16 | ||||
-rw-r--r-- | server/PhysicalNetwork.cpp | 30 | ||||
-rw-r--r-- | server/PhysicalNetwork.h | 13 | ||||
-rw-r--r-- | server/RouteController.cpp | 42 | ||||
-rw-r--r-- | server/RouteController.h | 5 |
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 |