OSDN Git Service

Fix missing errno.h includes after libc cleanup.
[android-x86/system-netd.git] / server / RouteController.cpp
index bc50dc4..4cb145b 100644 (file)
 
 #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
@@ -69,11 +84,18 @@ const uint8_t AF_FAMILIES[] = {AF_INET, AF_INET6};
 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) {
@@ -96,23 +118,65 @@ uint8_t PADDING_BUFFER[RTA_ALIGNTO] = {0, 0, 0, 0};
 
 // 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.
@@ -163,36 +227,53 @@ WARN_UNUSED_RESULT int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec
     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.
@@ -208,7 +289,8 @@ WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t
                                                                   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 },
@@ -225,9 +307,12 @@ WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t
         { &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;
@@ -241,6 +326,12 @@ WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t
     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,
@@ -267,29 +358,45 @@ WARN_UNUSED_RESULT int modifyIpRoute(uint16_t action, uint32_t table, const char
         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 };
@@ -313,58 +420,6 @@ WARN_UNUSED_RESULT int modifyIpRoute(uint16_t action, uint32_t table, const char
     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:
@@ -393,6 +448,66 @@ WARN_UNUSED_RESULT int modifyIncomingPacketMark(unsigned netId, const char* inte
     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.
@@ -416,7 +531,7 @@ WARN_UNUSED_RESULT int modifyExplicitNetworkRule(unsigned netId, uint32_t table,
     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.
@@ -433,7 +548,7 @@ WARN_UNUSED_RESULT int modifyOutputInterfaceRule(const char* interface, uint32_t
     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.
@@ -456,43 +571,112 @@ WARN_UNUSED_RESULT int modifyImplicitNetworkRule(unsigned netId, uint32_t table,
     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,
@@ -517,7 +701,7 @@ WARN_UNUSED_RESULT int modifyPhysicalNetwork(unsigned netId, const char* interfa
 }
 
 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) {
@@ -525,6 +709,9 @@ WARN_UNUSED_RESULT int modifyVirtualNetwork(unsigned netId, const char* interfac
     }
 
     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;
@@ -533,20 +720,19 @@ WARN_UNUSED_RESULT int modifyVirtualNetwork(unsigned netId, const char* interfac
                                                 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;
@@ -569,7 +755,35 @@ WARN_UNUSED_RESULT int modifyDefaultNetwork(uint16_t action, const char* interfa
     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
@@ -586,12 +800,16 @@ WARN_UNUSED_RESULT int modifyRoute(uint16_t action, const char* interface, const
             }
             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;
         }
     }
@@ -600,22 +818,11 @@ WARN_UNUSED_RESULT int modifyRoute(uint16_t action, const char* interface, const
     // 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;
 }
 
@@ -629,6 +836,7 @@ WARN_UNUSED_RESULT int flushRoutes(const char* interface) {
     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,
@@ -638,36 +846,73 @@ WARN_UNUSED_RESULT int flushRoutes(const char* interface) {
             "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,
@@ -675,22 +920,34 @@ int RouteController::removeInterfaceFromPhysicalNetwork(unsigned netId, const ch
     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,
@@ -703,15 +960,15 @@ int RouteController::modifyPhysicalNetworkPermission(unsigned netId, const char*
     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);
 }
 
@@ -733,3 +990,22 @@ int RouteController::removeRoute(const char* interface, const char* destination,
                                  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);
+}