#include "RouteController.h"
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fib_rules.h>
+#include <net/if.h>
+#include <sys/stat.h>
+
+#include <map>
+
#include "Fwmark.h"
#include "UidRanges.h"
#include "logwrap/logwrap.h"
#include "resolv_netid.h"
-#include <arpa/inet.h>
-#include <linux/fib_rules.h>
-#include <map>
-#include <net/if.h>
-
namespace {
// BEGIN CONSTANTS --------------------------------------------------------------------------------
const uint32_t RULE_PRIORITY_VPN_OVERRIDE_SYSTEM = 10000;
-// const uint32_t RULE_PRIORITY_VPN_OVERRIDE_LOCAL = 11000;
+const uint32_t RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL = 11000;
const uint32_t RULE_PRIORITY_SECURE_VPN = 12000;
const uint32_t RULE_PRIORITY_EXPLICIT_NETWORK = 13000;
const uint32_t RULE_PRIORITY_OUTPUT_INTERFACE = 14000;
const uint32_t RULE_PRIORITY_LEGACY_SYSTEM = 15000;
const uint32_t RULE_PRIORITY_LEGACY_NETWORK = 16000;
-// const uint32_t RULE_PRIORITY_LOCAL_NETWORK = 17000;
-// const uint32_t RULE_PRIORITY_TETHERING = 18000;
+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_BYPASSABLE_VPN = 20000;
+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;
+const uint32_t RULE_PRIORITY_UNREACHABLE = 32000;
+
+const uint32_t ROUTE_TABLE_LOCAL_NETWORK = 97;
+const uint32_t ROUTE_TABLE_LEGACY_NETWORK = 98;
+const uint32_t ROUTE_TABLE_LEGACY_SYSTEM = 99;
+
+const char* const ROUTE_TABLE_NAME_LOCAL_NETWORK = "local_network";
+const char* const ROUTE_TABLE_NAME_LEGACY_NETWORK = "legacy_network";
+const char* const ROUTE_TABLE_NAME_LEGACY_SYSTEM = "legacy_system";
+
+const char* const ROUTE_TABLE_NAME_LOCAL = "local";
+const char* const ROUTE_TABLE_NAME_MAIN = "main";
// TODO: These values aren't defined by the Linux kernel, because our UID routing changes are not
// upstream (yet?), so we can't just pick them up from kernel headers. When (if?) the changes make
const char* const IP_VERSIONS[] = {"-4", "-6"};
const uid_t UID_ROOT = 0;
+const char* const IIF_NONE = NULL;
const char* const OIF_NONE = NULL;
const bool ACTION_ADD = true;
const bool ACTION_DEL = false;
const bool MODIFY_NON_UID_BASED_RULES = true;
+const char* const RT_TABLES_PATH = "/data/misc/net/rt_tables";
+const int RT_TABLES_FLAGS = O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW | O_CLOEXEC;
+const mode_t RT_TABLES_MODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // mode 0644, rw-r--r--
+
+const unsigned ROUTE_FLUSH_ATTEMPTS = 2;
+
// Avoids "non-constant-expression cannot be narrowed from type 'unsigned int' to 'unsigned short'"
// warnings when using RTA_LENGTH(x) inside static initializers (even when x is already uint16_t).
constexpr uint16_t U16_RTA_LENGTH(uint16_t x) {
// END CONSTANTS ----------------------------------------------------------------------------------
-std::map<std::string, uint32_t> interfaceToIndex;
+// No locks needed because RouteController is accessed only from one thread (in CommandListener).
+std::map<std::string, uint32_t> interfaceToTable;
uint32_t getRouteTableForInterface(const char* interface) {
uint32_t index = if_nametoindex(interface);
if (index) {
- interfaceToIndex[interface] = index;
- } else {
- // If the interface goes away if_nametoindex() will return 0 but we still need to know
- // the index so we can remove the rules and routes.
- auto iter = interfaceToIndex.find(interface);
- if (iter == interfaceToIndex.end()) {
- ALOGE("cannot find interface %s", interface);
- return RT_TABLE_UNSPEC;
- }
- index = iter->second;
+ index += RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX;
+ interfaceToTable[interface] = index;
+ return index;
+ }
+ // If the interface goes away if_nametoindex() will return 0 but we still need to know
+ // the index so we can remove the rules and routes.
+ auto iter = interfaceToTable.find(interface);
+ if (iter == interfaceToTable.end()) {
+ ALOGE("cannot find interface %s", interface);
+ return RT_TABLE_UNSPEC;
+ }
+ return iter->second;
+}
+
+void addTableName(uint32_t table, const std::string& name, std::string* contents) {
+ char tableString[UINT32_STRLEN];
+ snprintf(tableString, sizeof(tableString), "%u", table);
+ *contents += tableString;
+ *contents += " ";
+ *contents += name;
+ *contents += "\n";
+}
+
+// Doesn't return success/failure as the file is optional; it's okay if we fail to update it.
+void updateTableNamesFile() {
+ std::string contents;
+
+ addTableName(RT_TABLE_LOCAL, ROUTE_TABLE_NAME_LOCAL, &contents);
+ addTableName(RT_TABLE_MAIN, ROUTE_TABLE_NAME_MAIN, &contents);
+
+ addTableName(ROUTE_TABLE_LOCAL_NETWORK, ROUTE_TABLE_NAME_LOCAL_NETWORK, &contents);
+ addTableName(ROUTE_TABLE_LEGACY_NETWORK, ROUTE_TABLE_NAME_LEGACY_NETWORK, &contents);
+ addTableName(ROUTE_TABLE_LEGACY_SYSTEM, ROUTE_TABLE_NAME_LEGACY_SYSTEM, &contents);
+
+ for (const auto& entry : interfaceToTable) {
+ addTableName(entry.second, entry.first, &contents);
+ }
+
+ int fd = open(RT_TABLES_PATH, RT_TABLES_FLAGS, RT_TABLES_MODE);
+ if (fd == -1) {
+ ALOGE("failed to create %s (%s)", RT_TABLES_PATH, strerror(errno));
+ return;
+ }
+ // File creation is affected by umask, so make sure the right mode bits are set.
+ if (fchmod(fd, RT_TABLES_MODE) == -1) {
+ ALOGE("failed to set mode 0%o on %s (%s)", RT_TABLES_MODE, RT_TABLES_PATH, strerror(errno));
}
- return index + RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX;
+ ssize_t bytesWritten = write(fd, contents.data(), contents.size());
+ if (bytesWritten != static_cast<ssize_t>(contents.size())) {
+ ALOGE("failed to write to %s (%zd vs %zu bytes) (%s)", RT_TABLES_PATH, bytesWritten,
+ contents.size(), strerror(errno));
+ }
+ close(fd);
}
// Sends a netlink request and expects an ack.
return ret;
}
+// Returns 0 on success or negative errno on failure.
+int padInterfaceName(const char* input, char* name, size_t* length, uint16_t* padding) {
+ if (!input) {
+ *length = 0;
+ *padding = 0;
+ return 0;
+ }
+ *length = strlcpy(name, input, IFNAMSIZ) + 1;
+ if (*length > IFNAMSIZ) {
+ ALOGE("interface name too long (%zu > %u)", *length, IFNAMSIZ);
+ return -ENAMETOOLONG;
+ }
+ *padding = RTA_SPACE(*length) - RTA_LENGTH(*length);
+ return 0;
+}
+
// Adds or removes a routing rule for IPv4 and IPv6.
//
// + If |table| is non-zero, the rule points at the specified routing table. Otherwise, the rule
// returns ENETUNREACH.
// + If |mask| is non-zero, the rule matches the specified fwmark and mask. Otherwise, |fwmark| is
// ignored.
-// + If |interface| is non-NULL, the rule matches the specified outgoing interface.
+// + If |iif| is non-NULL, the rule matches the specified incoming interface.
+// + If |oif| is non-NULL, the rule matches the specified outgoing interface.
+// + If |uidStart| and |uidEnd| are not INVALID_UID, the rule matches packets from UIDs in that
+// range (inclusive). Otherwise, the rule matches packets from all UIDs.
//
// Returns 0 on success or negative errno on failure.
WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table,
- uint32_t fwmark, uint32_t mask, const char* interface,
- uid_t uidStart, uid_t uidEnd) {
+ uint32_t fwmark, uint32_t mask, const char* iif,
+ const char* oif, uid_t uidStart, uid_t uidEnd) {
// Ensure that if you set a bit in the fwmark, it's not being ignored by the mask.
if (fwmark & ~mask) {
ALOGE("mask 0x%x does not select all the bits set in fwmark 0x%x", mask, fwmark);
return -ERANGE;
}
- // The interface name must include exactly one terminating NULL and be properly padded, or older
+ // Interface names must include exactly one terminating NULL and be properly padded, or older
// kernels will refuse to delete rules.
- uint16_t paddingLength = 0;
- size_t interfaceLength = 0;
- char oifname[IFNAMSIZ];
- if (interface != OIF_NONE) {
- interfaceLength = strlcpy(oifname, interface, IFNAMSIZ) + 1;
- if (interfaceLength > IFNAMSIZ) {
- ALOGE("interface name too long (%zu > %u)", interfaceLength, IFNAMSIZ);
- return -ENAMETOOLONG;
- }
- paddingLength = RTA_SPACE(interfaceLength) - RTA_LENGTH(interfaceLength);
+ char iifName[IFNAMSIZ], oifName[IFNAMSIZ];
+ size_t iifLength, oifLength;
+ uint16_t iifPadding, oifPadding;
+ if (int ret = padInterfaceName(iif, iifName, &iifLength, &iifPadding)) {
+ return ret;
+ }
+ if (int ret = padInterfaceName(oif, oifName, &oifLength, &oifPadding)) {
+ return ret;
}
// Either both start and end UID must be specified, or neither.
FR_ACT_UNREACHABLE),
};
- rtattr fraOifname = { U16_RTA_LENGTH(interfaceLength), FRA_OIFNAME };
+ rtattr fraIifName = { U16_RTA_LENGTH(iifLength), FRA_IIFNAME };
+ rtattr fraOifName = { U16_RTA_LENGTH(oifLength), FRA_OIFNAME };
iovec iov[] = {
{ NULL, 0 },
{ &uidStart, isUidRule ? sizeof(uidStart) : 0 },
{ &FRATTR_UID_END, isUidRule ? sizeof(FRATTR_UID_END) : 0 },
{ &uidEnd, isUidRule ? sizeof(uidEnd) : 0 },
- { &fraOifname, interface != OIF_NONE ? sizeof(fraOifname) : 0 },
- { oifname, interfaceLength },
- { PADDING_BUFFER, paddingLength },
+ { &fraIifName, iif != IIF_NONE ? sizeof(fraIifName) : 0 },
+ { iifName, iifLength },
+ { PADDING_BUFFER, iifPadding },
+ { &fraOifName, oif != OIF_NONE ? sizeof(fraOifName) : 0 },
+ { oifName, oifLength },
+ { PADDING_BUFFER, oifPadding },
};
uint16_t flags = (action == RTM_NEWRULE) ? NETLINK_CREATE_REQUEST_FLAGS : NETLINK_REQUEST_FLAGS;
return 0;
}
+WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table,
+ uint32_t fwmark, uint32_t mask) {
+ return modifyIpRule(action, priority, table, fwmark, mask, IIF_NONE, OIF_NONE, INVALID_UID,
+ INVALID_UID);
+}
+
// Adds or deletes an IPv4 or IPv6 route.
// Returns 0 on success or negative errno on failure.
WARN_UNUSED_RESULT int modifyIpRoute(uint16_t action, uint32_t table, const char* interface,
return -ENOBUFS; // Cannot happen; parsePrefix only supports IPv4 and IPv6.
}
- // If an interface was specified, find the ifindex.
+ uint8_t type = RTN_UNICAST;
uint32_t ifindex;
- if (interface != OIF_NONE) {
- ifindex = if_nametoindex(interface);
- if (!ifindex) {
- ALOGE("cannot find interface %s", interface);
- return -ENODEV;
+ uint8_t rawNexthop[sizeof(in6_addr)];
+
+ if (nexthop && !strcmp(nexthop, "unreachable")) {
+ type = RTN_UNREACHABLE;
+ // 'interface' is likely non-NULL, as the caller (modifyRoute()) likely used it to lookup
+ // the table number. But it's an error to specify an interface ("dev ...") or a nexthop for
+ // unreachable routes, so nuke them. (IPv6 allows them to be specified; IPv4 doesn't.)
+ interface = OIF_NONE;
+ nexthop = NULL;
+ } else if (nexthop && !strcmp(nexthop, "throw")) {
+ type = RTN_THROW;
+ interface = OIF_NONE;
+ nexthop = NULL;
+ } else {
+ // If an interface was specified, find the ifindex.
+ if (interface != OIF_NONE) {
+ ifindex = if_nametoindex(interface);
+ if (!ifindex) {
+ ALOGE("cannot find interface %s", interface);
+ return -ENODEV;
+ }
}
- }
- // If a nexthop was specified, parse it as the same family as the prefix.
- uint8_t rawNexthop[sizeof(in6_addr)];
- if (nexthop && inet_pton(family, nexthop, rawNexthop) <= 0) {
- ALOGE("inet_pton failed for nexthop %s", nexthop);
- return -EINVAL;
+ // If a nexthop was specified, parse it as the same family as the prefix.
+ if (nexthop && inet_pton(family, nexthop, rawNexthop) <= 0) {
+ ALOGE("inet_pton failed for nexthop %s", nexthop);
+ return -EINVAL;
+ }
}
// Assemble a rtmsg and put it in an array of iovec structures.
rtmsg route = {
.rtm_protocol = RTPROT_STATIC,
- .rtm_type = RTN_UNICAST,
+ .rtm_type = type,
.rtm_family = family,
.rtm_dst_len = prefixLength,
+ .rtm_scope = static_cast<uint8_t>(nexthop ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK),
};
rtattr rtaDst = { U16_RTA_LENGTH(rawLength), RTA_DST };
return sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov));
}
-// Add rules to allow legacy routes added through the requestRouteToHost() API.
-WARN_UNUSED_RESULT int AddLegacyRouteRules() {
- Fwmark fwmark;
- Fwmark mask;
-
- fwmark.explicitlySelected = false;
- mask.explicitlySelected = true;
-
- // Rules to allow legacy routes to override the default network.
- if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY_SYSTEM,
- RouteController::ROUTE_TABLE_LEGACY_SYSTEM, fwmark.intValue,
- mask.intValue, OIF_NONE, INVALID_UID, INVALID_UID)) {
- return ret;
- }
- if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY_NETWORK,
- RouteController::ROUTE_TABLE_LEGACY_NETWORK, fwmark.intValue,
- mask.intValue, OIF_NONE, INVALID_UID, INVALID_UID)) {
- return ret;
- }
-
- fwmark.permission = PERMISSION_SYSTEM;
- mask.permission = PERMISSION_SYSTEM;
-
- // A rule to allow legacy routes from system apps to override VPNs.
- return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_VPN_OVERRIDE_SYSTEM,
- RouteController::ROUTE_TABLE_LEGACY_SYSTEM, fwmark.intValue, mask.intValue,
- OIF_NONE, INVALID_UID, INVALID_UID);
-}
-
-// Add a new rule to look up the 'main' table, with the same selectors as the "default network"
-// rule, but with a lower priority. Since the default network rule points to a table with a default
-// route, the rule we're adding will never be used for normal routing lookups. However, the kernel
-// may fall-through to it to find directly-connected routes when it validates that a nexthop (in a
-// route being added) is reachable.
-WARN_UNUSED_RESULT int AddDirectlyConnectedRule() {
- Fwmark fwmark;
- Fwmark mask;
-
- fwmark.netId = NETID_UNSET;
- mask.netId = FWMARK_NET_ID_MASK;
-
- return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_DIRECTLY_CONNECTED, RT_TABLE_MAIN,
- fwmark.intValue, mask.intValue, OIF_NONE, UID_ROOT, UID_ROOT);
-}
-
-// Add a rule to preempt the pre-defined "from all lookup main" rule. Packets that reach this rule
-// will be null-routed, and won't fall-through to the main table.
-WARN_UNUSED_RESULT int AddUnreachableRule() {
- return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_UNREACHABLE, RT_TABLE_UNSPEC, MARK_UNSET,
- MARK_UNSET, OIF_NONE, INVALID_UID, INVALID_UID);
-}
-
// An iptables rule to mark incoming packets on a network with the netId of the network.
//
// This is so that the kernel can:
return 0;
}
+// A rule to route responses to the local network forwarded via the VPN.
+//
+// When a VPN is in effect, packets from the local network to upstream networks are forwarded into
+// the VPN's tunnel interface. When the VPN forwards the responses, they emerge out of the tunnel.
+WARN_UNUSED_RESULT int modifyVpnOutputToLocalRule(const char* vpnInterface, bool add) {
+ return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL,
+ ROUTE_TABLE_LOCAL_NETWORK, MARK_UNSET, MARK_UNSET, vpnInterface, OIF_NONE,
+ INVALID_UID, INVALID_UID);
+}
+
+// A rule to route all traffic from a given set of UIDs to go over the VPN.
+//
+// Notice that this rule doesn't use the netId. I.e., no matter what netId the user's socket may
+// have, if they are subject to this VPN, their traffic has to go through it. Allows the traffic to
+// bypass the VPN if the protectedFromVpn bit is set.
+WARN_UNUSED_RESULT int modifyVpnUidRangeRule(uint32_t table, uid_t uidStart, uid_t uidEnd,
+ bool secure, bool add) {
+ Fwmark fwmark;
+ Fwmark mask;
+
+ fwmark.protectedFromVpn = false;
+ mask.protectedFromVpn = true;
+
+ uint32_t priority;
+
+ if (secure) {
+ priority = RULE_PRIORITY_SECURE_VPN;
+ } else {
+ priority = RULE_PRIORITY_BYPASSABLE_VPN;
+
+ fwmark.explicitlySelected = false;
+ mask.explicitlySelected = true;
+ }
+
+ return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, priority, table, fwmark.intValue,
+ mask.intValue, IIF_NONE, OIF_NONE, uidStart, uidEnd);
+}
+
+// A rule to allow system apps to send traffic over this VPN even if they are not part of the target
+// set of UIDs.
+//
+// This is needed for DnsProxyListener to correctly resolve a request for a user who is in the
+// target set, but where the DnsProxyListener itself is not.
+WARN_UNUSED_RESULT int modifyVpnSystemPermissionRule(unsigned netId, uint32_t table, bool secure,
+ bool add) {
+ Fwmark fwmark;
+ Fwmark mask;
+
+ fwmark.netId = netId;
+ mask.netId = FWMARK_NET_ID_MASK;
+
+ fwmark.permission = PERMISSION_SYSTEM;
+ mask.permission = PERMISSION_SYSTEM;
+
+ uint32_t priority = secure ? RULE_PRIORITY_SECURE_VPN : RULE_PRIORITY_BYPASSABLE_VPN;
+
+ return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, priority, table, fwmark.intValue,
+ mask.intValue);
+}
+
// A rule to route traffic based on an explicitly chosen network.
//
// Supports apps that use the multinetwork APIs to restrict their traffic to a network.
mask.permission = permission;
return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_EXPLICIT_NETWORK, table,
- fwmark.intValue, mask.intValue, OIF_NONE, uidStart, uidEnd);
+ fwmark.intValue, mask.intValue, IIF_NONE, OIF_NONE, uidStart, uidEnd);
}
// A rule to route traffic based on a chosen outgoing interface.
mask.permission = permission;
return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_OUTPUT_INTERFACE, table,
- fwmark.intValue, mask.intValue, interface, uidStart, uidEnd);
+ fwmark.intValue, mask.intValue, IIF_NONE, interface, uidStart, uidEnd);
}
// A rule to route traffic based on the chosen network.
mask.permission = permission;
return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_IMPLICIT_NETWORK, table,
- fwmark.intValue, mask.intValue, OIF_NONE, INVALID_UID, INVALID_UID);
+ fwmark.intValue, mask.intValue);
}
-// A rule to route all traffic from a given set of UIDs to go over the VPN.
+// A rule to enable split tunnel VPNs.
//
-// Notice that this rule doesn't use the netId. I.e., no matter what netId the user's socket may
-// have, if they are subject to this VPN, their traffic has to go through it. Allows the traffic to
-// bypass the VPN if the protectedFromVpn bit is set.
-WARN_UNUSED_RESULT int modifyVpnUidRangeRule(uint32_t table, uid_t uidStart, uid_t uidEnd,
- bool add) {
+// 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.protectedFromVpn = false;
- mask.protectedFromVpn = true;
+ fwmark.netId = vpnNetId;
+ mask.netId = FWMARK_NET_ID_MASK;
+
+ fwmark.explicitlySelected = false;
+ mask.explicitlySelected = true;
- return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_SECURE_VPN, table,
- fwmark.intValue, mask.intValue, OIF_NONE, uidStart, uidEnd);
+ fwmark.permission = permission;
+ mask.permission = permission;
+
+ return modifyIpRule(action, RULE_PRIORITY_VPN_FALLTHROUGH, table, fwmark.intValue,
+ mask.intValue);
}
-// A rule to allow system apps to send traffic over this VPN even if they are not part of the target
-// set of UIDs.
-//
-// This is needed for DnsProxyListener to correctly resolve a request for a user who is in the
-// target set, but where the DnsProxyListener itself is not.
-WARN_UNUSED_RESULT int modifyVpnSystemPermissionRule(unsigned netId, uint32_t table, bool add) {
+// Add rules to allow legacy routes added through the requestRouteToHost() API.
+WARN_UNUSED_RESULT int addLegacyRouteRules() {
Fwmark fwmark;
Fwmark mask;
- fwmark.netId = netId;
- mask.netId = FWMARK_NET_ID_MASK;
+ fwmark.explicitlySelected = false;
+ mask.explicitlySelected = true;
+
+ // Rules to allow legacy routes to override the default network.
+ if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY_SYSTEM, ROUTE_TABLE_LEGACY_SYSTEM,
+ fwmark.intValue, mask.intValue)) {
+ return ret;
+ }
+ if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY_NETWORK,
+ ROUTE_TABLE_LEGACY_NETWORK, fwmark.intValue, mask.intValue)) {
+ return ret;
+ }
fwmark.permission = PERMISSION_SYSTEM;
mask.permission = PERMISSION_SYSTEM;
- return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_SECURE_VPN, table,
- fwmark.intValue, mask.intValue, OIF_NONE, INVALID_UID, INVALID_UID);
+ // A rule to allow legacy routes from system apps to override VPNs.
+ return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_VPN_OVERRIDE_SYSTEM, ROUTE_TABLE_LEGACY_SYSTEM,
+ fwmark.intValue, mask.intValue);
+}
+
+// Add rules to lookup the local network when specified explicitly or otherwise.
+WARN_UNUSED_RESULT int addLocalNetworkRules(unsigned localNetId) {
+ if (int ret = modifyExplicitNetworkRule(localNetId, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE,
+ INVALID_UID, INVALID_UID, ACTION_ADD)) {
+ return ret;
+ }
+
+ Fwmark fwmark;
+ Fwmark mask;
+
+ fwmark.explicitlySelected = false;
+ mask.explicitlySelected = true;
+
+ return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LOCAL_NETWORK, ROUTE_TABLE_LOCAL_NETWORK,
+ fwmark.intValue, mask.intValue);
+}
+
+// Add a new rule to look up the 'main' table, with the same selectors as the "default network"
+// rule, but with a lower priority. We will never create routes in the main table; it should only be
+// used for directly-connected routes implicitly created by the kernel when adding IP addresses.
+// This is necessary, for example, when adding a route through a directly-connected gateway: in
+// order to add the route, there must already be a directly-connected route that covers the gateway.
+WARN_UNUSED_RESULT int addDirectlyConnectedRule() {
+ Fwmark fwmark;
+ Fwmark mask;
+
+ fwmark.netId = NETID_UNSET;
+ mask.netId = FWMARK_NET_ID_MASK;
+
+ return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_DIRECTLY_CONNECTED, RT_TABLE_MAIN,
+ fwmark.intValue, mask.intValue, IIF_NONE, OIF_NONE, UID_ROOT, UID_ROOT);
+}
+
+// Add an explicit unreachable rule close to the end of the prioriy list to make it clear that
+// relying on the kernel-default "from all lookup main" rule at priority 32766 is not intended
+// behaviour. We do flush the kernel-default rules at startup, but having an explicit unreachable
+// rule will hopefully make things even clearer.
+WARN_UNUSED_RESULT int addUnreachableRule() {
+ return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_UNREACHABLE, RT_TABLE_UNSPEC, MARK_UNSET,
+ MARK_UNSET);
+}
+
+WARN_UNUSED_RESULT int modifyLocalNetwork(unsigned netId, const char* interface, bool add) {
+ if (int ret = modifyIncomingPacketMark(netId, interface, PERMISSION_NONE, add)) {
+ return ret;
+ }
+ return modifyOutputInterfaceRule(interface, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE,
+ INVALID_UID, INVALID_UID, add);
}
WARN_UNUSED_RESULT int modifyPhysicalNetwork(unsigned netId, const char* interface,
}
WARN_UNUSED_RESULT int modifyVirtualNetwork(unsigned netId, const char* interface,
- const UidRanges& uidRanges, bool add,
+ const UidRanges& uidRanges, bool secure, bool add,
bool modifyNonUidBasedRules) {
uint32_t table = getRouteTableForInterface(interface);
if (table == RT_TABLE_UNSPEC) {
}
for (const UidRanges::Range& range : uidRanges.getRanges()) {
+ if (int ret = modifyVpnUidRangeRule(table, range.first, range.second, secure, add)) {
+ return ret;
+ }
if (int ret = modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, range.first,
range.second, add)) {
return ret;
range.second, add)) {
return ret;
}
- if (int ret = modifyVpnUidRangeRule(table, range.first, range.second, add)) {
- return ret;
- }
}
if (modifyNonUidBasedRules) {
if (int ret = modifyIncomingPacketMark(netId, interface, PERMISSION_NONE, add)) {
return ret;
}
- if (int ret = modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, UID_ROOT, UID_ROOT,
- add)) {
+ if (int ret = modifyVpnOutputToLocalRule(interface, add)) {
+ return ret;
+ }
+ if (int ret = modifyVpnSystemPermissionRule(netId, table, secure, add)) {
return ret;
}
- return modifyVpnSystemPermissionRule(netId, table, add);
+ return modifyExplicitNetworkRule(netId, table, PERMISSION_NONE, UID_ROOT, UID_ROOT, add);
}
return 0;
mask.permission = permission;
return modifyIpRule(action, RULE_PRIORITY_DEFAULT_NETWORK, table, fwmark.intValue,
- mask.intValue, OIF_NONE, INVALID_UID, INVALID_UID);
+ mask.intValue);
+}
+
+WARN_UNUSED_RESULT int modifyTetheredNetwork(uint16_t action, const char* inputInterface,
+ const char* outputInterface) {
+ uint32_t table = getRouteTableForInterface(outputInterface);
+ if (table == RT_TABLE_UNSPEC) {
+ return -ESRCH;
+ }
+
+ return modifyIpRule(action, RULE_PRIORITY_TETHERING, table, MARK_UNSET, MARK_UNSET,
+ inputInterface, OIF_NONE, INVALID_UID, INVALID_UID);
+}
+
+// Returns 0 on success or negative errno on failure.
+WARN_UNUSED_RESULT int flushRules() {
+ for (size_t i = 0; i < ARRAY_SIZE(IP_VERSIONS); ++i) {
+ const char* argv[] = {
+ IP_PATH,
+ IP_VERSIONS[i],
+ "rule",
+ "flush",
+ };
+ if (android_fork_execvp(ARRAY_SIZE(argv), const_cast<char**>(argv), NULL, false, false)) {
+ ALOGE("failed to flush rules");
+ return -EREMOTEIO;
+ }
+ }
+ return 0;
}
// Adds or removes an IPv4 or IPv6 route to the specified table and, if it's a directly-connected
}
break;
}
+ case RouteController::LOCAL_NETWORK: {
+ table = ROUTE_TABLE_LOCAL_NETWORK;
+ break;
+ }
case RouteController::LEGACY_NETWORK: {
- table = RouteController::ROUTE_TABLE_LEGACY_NETWORK;
+ table = ROUTE_TABLE_LEGACY_NETWORK;
break;
}
case RouteController::LEGACY_SYSTEM: {
- table = RouteController::ROUTE_TABLE_LEGACY_SYSTEM;
+ table = ROUTE_TABLE_LEGACY_SYSTEM;
break;
}
}
// We allow apps to call requestRouteToHost() multiple times with the same route, so ignore
// EEXIST failures when adding routes to legacy tables.
if (ret && !(action == RTM_NEWROUTE && ret == -EEXIST &&
- tableType != RouteController::INTERFACE)) {
+ (tableType == RouteController::LEGACY_NETWORK ||
+ tableType == RouteController::LEGACY_SYSTEM))) {
return ret;
}
- // If there's no nexthop, this is a directly connected route. Add it to the main table also, to
- // let the kernel find it when validating nexthops when global routes are added.
- if (!nexthop) {
- ret = modifyIpRoute(action, RT_TABLE_MAIN, interface, destination, NULL);
- // A failure with action == ADD && errno == EEXIST means that the route already exists in
- // the main table, perhaps because the kernel added it automatically as part of adding the
- // IP address to the interface. Ignore this, but complain about everything else.
- if (ret && !(action == RTM_NEWROUTE && ret == -EEXIST)) {
- return ret;
- }
- }
-
return 0;
}
char tableString[UINT32_STRLEN];
snprintf(tableString, sizeof(tableString), "%u", table);
+ int ret = 0;
for (size_t i = 0; i < ARRAY_SIZE(IP_VERSIONS); ++i) {
const char* argv[] = {
IP_PATH,
"table",
tableString,
};
- if (android_fork_execvp(ARRAY_SIZE(argv), const_cast<char**>(argv), NULL, false, false)) {
- ALOGE("failed to flush routes");
- return -EREMOTEIO;
+
+ // A flush works by dumping routes and deleting each route as it's returned, and it can
+ // fail if something else deletes the route between the dump and the delete. This can
+ // happen, for example, if an interface goes down while we're trying to flush its routes.
+ // So try multiple times and only return an error if the last attempt fails.
+ //
+ // TODO: replace this with our own netlink code.
+ unsigned attempts = 0;
+ int err;
+ do {
+ err = android_fork_execvp(ARRAY_SIZE(argv), const_cast<char**>(argv),
+ NULL, false, false);
+ ++attempts;
+ } while (err != 0 && attempts < ROUTE_FLUSH_ATTEMPTS);
+ if (err) {
+ ALOGE("failed to flush %s routes in table %s after %d attempts",
+ IP_VERSIONS[i], tableString, attempts);
+ ret = -EREMOTEIO;
}
}
- interfaceToIndex.erase(interface);
- return 0;
+ // If we failed to flush routes, the caller may elect to keep this interface around, so keep
+ // track of its name.
+ if (!ret) {
+ interfaceToTable.erase(interface);
+ }
+
+ return ret;
}
} // namespace
-int RouteController::Init() {
- if (int ret = AddDirectlyConnectedRule()) {
+int RouteController::Init(unsigned localNetId) {
+ if (int ret = flushRules()) {
return ret;
}
-
- if (int ret = AddLegacyRouteRules()) {
+ if (int ret = addLegacyRouteRules()) {
+ return ret;
+ }
+ if (int ret = addLocalNetworkRules(localNetId)) {
+ return ret;
+ }
+ if (int ret = addDirectlyConnectedRule()) {
return ret;
}
- // TODO: Enable once we are sure everything works.
- if (false) {
- return AddUnreachableRule();
+ if (int ret = addUnreachableRule()) {
+ return ret;
}
+ updateTableNamesFile();
return 0;
}
+int RouteController::addInterfaceToLocalNetwork(unsigned netId, const char* interface) {
+ return modifyLocalNetwork(netId, interface, ACTION_ADD);
+}
+
+int RouteController::removeInterfaceFromLocalNetwork(unsigned netId, const char* interface) {
+ return modifyLocalNetwork(netId, interface, ACTION_DEL);
+}
+
int RouteController::addInterfaceToPhysicalNetwork(unsigned netId, const char* interface,
Permission permission) {
- return modifyPhysicalNetwork(netId, interface, permission, ACTION_ADD);
+ if (int ret = modifyPhysicalNetwork(netId, interface, permission, ACTION_ADD)) {
+ return ret;
+ }
+ updateTableNamesFile();
+ return 0;
}
int RouteController::removeInterfaceFromPhysicalNetwork(unsigned netId, const char* interface,
if (int ret = modifyPhysicalNetwork(netId, interface, permission, ACTION_DEL)) {
return ret;
}
- return flushRoutes(interface);
+ if (int ret = flushRoutes(interface)) {
+ return ret;
+ }
+ updateTableNamesFile();
+ return 0;
}
int RouteController::addInterfaceToVirtualNetwork(unsigned netId, const char* interface,
- const UidRanges& uidRanges) {
- return modifyVirtualNetwork(netId, interface, uidRanges, ACTION_ADD,
- MODIFY_NON_UID_BASED_RULES);
+ bool secure, const UidRanges& uidRanges) {
+ if (int ret = modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_ADD,
+ MODIFY_NON_UID_BASED_RULES)) {
+ return ret;
+ }
+ updateTableNamesFile();
+ return 0;
}
int RouteController::removeInterfaceFromVirtualNetwork(unsigned netId, const char* interface,
- const UidRanges& uidRanges) {
- if (int ret = modifyVirtualNetwork(netId, interface, uidRanges, ACTION_DEL,
+ bool secure, const UidRanges& uidRanges) {
+ if (int ret = modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_DEL,
MODIFY_NON_UID_BASED_RULES)) {
return ret;
}
- return flushRoutes(interface);
+ if (int ret = flushRoutes(interface)) {
+ return ret;
+ }
+ updateTableNamesFile();
+ return 0;
}
int RouteController::modifyPhysicalNetworkPermission(unsigned netId, const char* interface,
return modifyPhysicalNetwork(netId, interface, oldPermission, ACTION_DEL);
}
-int RouteController::addUsersToVirtualNetwork(unsigned netId, const char* interface,
+int RouteController::addUsersToVirtualNetwork(unsigned netId, const char* interface, bool secure,
const UidRanges& uidRanges) {
- return modifyVirtualNetwork(netId, interface, uidRanges, ACTION_ADD,
+ return modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_ADD,
!MODIFY_NON_UID_BASED_RULES);
}
int RouteController::removeUsersFromVirtualNetwork(unsigned netId, const char* interface,
- const UidRanges& uidRanges) {
- return modifyVirtualNetwork(netId, interface, uidRanges, ACTION_DEL,
+ bool secure, const UidRanges& uidRanges) {
+ return modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_DEL,
!MODIFY_NON_UID_BASED_RULES);
}
const char* nexthop, TableType tableType) {
return modifyRoute(RTM_DELROUTE, interface, destination, nexthop, tableType);
}
+
+int RouteController::enableTethering(const char* inputInterface, const char* outputInterface) {
+ return modifyTetheredNetwork(RTM_NEWRULE, inputInterface, outputInterface);
+}
+
+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);
+}