Support legacy routes added by apps via ensureRouteToHost().

This adds the routes to two fixed tables:
+ LEGACY, which has higher priority than other non-explicit lookup tables
  (per-network and default network).
+ PRIVILEGED_LEGACY, available only to system apps and has higher priority than
  VPNs (system apps are those with the CONNECTIVITY_INTERNAL permission).

This will be changed to per-UID tables once the kernel supports UID-based
routing, so that these legacy routes are scoped to each app and not global.

Also, fix a TODO: The framework (as of http://ag/471599) will not set the
gateway argument if it's actually a direct-connected route.

Change-Id: I0ee1ca89fdc859d75a89021ca8c1902811b1e4a9
diff --git a/server/CommandListener.cpp b/server/CommandListener.cpp
index fbf5670..72ff411 100644
--- a/server/CommandListener.cpp
+++ b/server/CommandListener.cpp
@@ -1619,8 +1619,8 @@
         return success(client);
     }
 
-    //    0      1         2         3
-    // network addiface <netId> <interface>
+    //    0         1         2         3
+    // network   addiface  <netId> <interface>
     // network removeiface <netId> <interface>
     if (!strcmp(argv[1], "addiface") || !strcmp(argv[1], "removeiface")) {
         if (argc != 4) {
@@ -1705,40 +1705,48 @@
         return success(client);
     }
 
-    //    0      1     2       3         4            5           6
-    // network route  add   <netId> <interface> <destination> [nexthop]
-    // network route remove <netId> <interface> <destination> [nexthop]
+    //    0      1      2      3      4       5         6            7           8
+    // network route [legacy <uid>]  add   <netId> <interface> <destination> [nexthop]
+    // network route [legacy <uid>] remove <netId> <interface> <destination> [nexthop]
     if (!strcmp(argv[1], "route")) {
-        if (argc < 6 || argc > 7) {
+        if (argc < 6 || argc > 9) {
             return syntaxError(client, "Incorrect number of arguments");
         }
-        // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
-        unsigned netId = strtoul(argv[3], NULL, 0);
-        const char* interface = argv[4];
-        const char* destination = argv[5];
-        const char* nexthop = NULL;
-        // If the nexthop (gateway) is equal to INADDR_ANY or in6addr_any, the route is a directly
-        // connected route, and we should not set nexthop.
-        // TODO: This doesn't catch all the ways of representing the "any" address (e.g.: "0/0",
-        // "::0", etc). Fix the callers to not pass us a nexthop in the first place in such cases.
-        if (argc == 7 && strcmp(argv[6], "0.0.0.0") && strcmp(argv[6], "::")) {
-            nexthop = argv[6];
+
+        int nextArg = 2;
+        bool legacy = false;
+        unsigned uid = 0;
+        if (!strcmp(argv[nextArg], "legacy")) {
+            ++nextArg;
+            legacy = true;
+            uid = strtoul(argv[nextArg++], NULL, 0);
         }
-        if (!strcmp(argv[2], "add")) {
-            if (!sNetCtrl->addRoute(netId, interface, destination, nexthop)) {
-                return operationError(client, "addRoute() failed");
-            }
-        } else if (!strcmp(argv[2], "remove")) {
-            if (!sNetCtrl->removeRoute(netId, interface, destination, nexthop)) {
-                return operationError(client, "removeRoute() failed");
-            }
-        } else {
+
+        bool add = false;
+        if (!strcmp(argv[nextArg], "add")) {
+            add = true;
+        } else if (strcmp(argv[nextArg], "remove")) {
             return syntaxError(client, "Unknown argument");
         }
+        ++nextArg;
+
+        // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
+        unsigned netId = strtoul(argv[nextArg++], NULL, 0);
+        const char* interface = argv[nextArg++];
+        const char* destination = argv[nextArg++];
+        const char* nexthop = argc > nextArg ? argv[nextArg] : NULL;
+
+        if (add) {
+            if (!sNetCtrl->addRoute(netId, interface, destination, nexthop, legacy, uid)) {
+                return operationError(client, "addRoute() failed");
+            }
+        } else if (!sNetCtrl->removeRoute(netId, interface, destination, nexthop, legacy, uid)) {
+            return operationError(client, "removeRoute() failed");
+        }
+
         return success(client);
     }
 
-    // network legacy <uid> route <add|remove> <other-route-params>
     // network vpn create <netId> [owner_uid]
     // network vpn destroy <netId>
     // network <bind|unbind> <netId> <uid1> .. <uidN>
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index 9504dc0..bf6383e 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -325,13 +325,13 @@
 }
 
 bool NetworkController::addRoute(unsigned netId, const char* interface, const char* destination,
-                                 const char* nexthop) {
-    return modifyRoute(netId, interface, destination, nexthop, true);
+                                 const char* nexthop, bool legacy, unsigned uid) {
+    return modifyRoute(netId, interface, destination, nexthop, true, legacy, uid);
 }
 
 bool NetworkController::removeRoute(unsigned netId, const char* interface, const char* destination,
-                                    const char* nexthop) {
-    return modifyRoute(netId, interface, destination, nexthop, false);
+                                    const char* nexthop, bool legacy, unsigned uid) {
+    return modifyRoute(netId, interface, destination, nexthop, false, legacy, uid);
 }
 
 bool NetworkController::isValidNetwork(unsigned netId) const {
@@ -344,7 +344,7 @@
 }
 
 bool NetworkController::modifyRoute(unsigned netId, const char* interface, const char* destination,
-                                    const char* nexthop, bool add) {
+                                    const char* nexthop, bool add, bool legacy, unsigned uid) {
     if (!isValidNetwork(netId)) {
         ALOGE("invalid netId %u", netId);
         return false;
@@ -355,8 +355,19 @@
         return false;
     }
 
-    return add ? mRouteController->addRoute(interface, destination, nexthop) :
-                 mRouteController->removeRoute(interface, destination, nexthop);
+    RouteController::TableType tableType;
+    if (legacy) {
+        if (mPermissionsController->getPermissionForUser(uid) & PERMISSION_CONNECTIVITY_INTERNAL) {
+            tableType = RouteController::PRIVILEGED_LEGACY;
+        } else {
+            tableType = RouteController::LEGACY;
+        }
+    } else {
+        tableType = RouteController::INTERFACE;
+    }
+
+    return add ? mRouteController->addRoute(interface, destination, nexthop, tableType, uid) :
+                 mRouteController->removeRoute(interface, destination, nexthop, tableType, uid);
 }
 
 NetworkController::UidEntry::UidEntry(int start, int end, unsigned netId, bool forward_dns)
diff --git a/server/NetworkController.h b/server/NetworkController.h
index 27785d3..30c9142 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -68,9 +68,9 @@
     // Routes are added to tables determined by the interface, so only |interface| is actually used.
     // |netId| is given only to sanity check that the interface has the correct netId.
     bool addRoute(unsigned netId, const char* interface, const char* destination,
-                  const char* nexthop);
+                  const char* nexthop, bool legacy, unsigned uid);
     bool removeRoute(unsigned netId, const char* interface, const char* destination,
-                     const char* nexthop);
+                     const char* nexthop, bool legacy, unsigned uid);
 
     bool isValidNetwork(unsigned netId) const;
 
@@ -80,7 +80,7 @@
     typedef std::pair<InterfaceIterator, InterfaceIterator> InterfaceRange;
 
     bool modifyRoute(unsigned netId, const char* interface, const char* destination,
-                     const char* nexthop, bool add);
+                     const char* nexthop, bool add, bool legacy, unsigned uid);
 
     struct UidEntry {
         int uid_start;
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index 7f9ce39..36773d7 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -25,13 +25,19 @@
 
 namespace {
 
+const uint32_t RULE_PRIORITY_PRIVILEGED_LEGACY     = 11000;
 const uint32_t RULE_PRIORITY_PER_NETWORK_EXPLICIT  = 13000;
 const uint32_t RULE_PRIORITY_PER_NETWORK_INTERFACE = 14000;
+const uint32_t RULE_PRIORITY_LEGACY                = 16000;
 const uint32_t RULE_PRIORITY_PER_NETWORK_NORMAL    = 17000;
 const uint32_t RULE_PRIORITY_DEFAULT_NETWORK       = 19000;
 const uint32_t RULE_PRIORITY_MAIN                  = 20000;
 const uint32_t RULE_PRIORITY_UNREACHABLE           = 21000;
 
+// TODO: These should be turned into per-UID tables once the kernel supports UID-based routing.
+const int ROUTE_TABLE_PRIVILEGED_LEGACY = RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX - 901;
+const int ROUTE_TABLE_LEGACY            = RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX - 902;
+
 uint32_t getRouteTableForInterface(const char* interface) {
     uint32_t index = if_nametoindex(interface);
     return index ? index + RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX : 0;
@@ -202,8 +208,24 @@
 }
 
 bool modifyRoute(const char* interface, const char* destination, const char* nexthop,
-                 const char* action) {
-    uint32_t table = getRouteTableForInterface(interface);
+                 const char* action, RouteController::TableType tableType, unsigned /* uid */) {
+    uint32_t table = 0;
+    switch (tableType) {
+        case RouteController::INTERFACE: {
+            table = getRouteTableForInterface(interface);
+            break;
+        }
+        case RouteController::LEGACY: {
+            // TODO: Use the UID to assign a unique table per UID instead of this fixed table.
+            table = ROUTE_TABLE_LEGACY;
+            break;
+        }
+        case RouteController::PRIVILEGED_LEGACY: {
+            // TODO: Use the UID to assign a unique table per UID instead of this fixed table.
+            table = ROUTE_TABLE_PRIVILEGED_LEGACY;
+            break;
+        }
+    }
     if (!table) {
         return false;
     }
@@ -252,6 +274,25 @@
 
     runIpRuleCommand(ADD, RULE_PRIORITY_MAIN, RT_TABLE_MAIN, fwmark.intValue, mask.intValue, NULL);
 
+    // Add rules to allow lookup of legacy routes.
+    //
+    // TODO: Remove these once the kernel supports UID-based routing. Instead, add them on demand
+    // when routes are added.
+    fwmark.netId = 0;
+    mask.netId = 0;
+
+    fwmark.explicitlySelected = false;
+    mask.explicitlySelected = true;
+
+    runIpRuleCommand(ADD, RULE_PRIORITY_LEGACY, ROUTE_TABLE_LEGACY, fwmark.intValue, mask.intValue,
+                     NULL);
+
+    fwmark.permission = PERMISSION_CONNECTIVITY_INTERNAL;
+    mask.permission = PERMISSION_CONNECTIVITY_INTERNAL;
+
+    runIpRuleCommand(ADD, RULE_PRIORITY_PRIVILEGED_LEGACY, ROUTE_TABLE_PRIVILEGED_LEGACY,
+                     fwmark.intValue, mask.intValue, NULL);
+
 // TODO: Uncomment once we are sure everything works.
 #if 0
     // Add a rule to preempt the pre-defined "from all lookup main" rule. This ensures that packets
@@ -287,11 +328,11 @@
 }
 
 bool RouteController::addRoute(const char* interface, const char* destination,
-                               const char* nexthop) {
-    return modifyRoute(interface, destination, nexthop, ADD);
+                               const char* nexthop, TableType tableType, unsigned uid) {
+    return modifyRoute(interface, destination, nexthop, ADD, tableType, uid);
 }
 
 bool RouteController::removeRoute(const char* interface, const char* destination,
-                                  const char* nexthop) {
-    return modifyRoute(interface, destination, nexthop, DEL);
+                                  const char* nexthop, TableType tableType, unsigned uid) {
+    return modifyRoute(interface, destination, nexthop, DEL, tableType, uid);
 }
diff --git a/server/RouteController.h b/server/RouteController.h
index 6d66ed7..7292579 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -21,6 +21,13 @@
 
 class RouteController {
 public:
+    // How the routing table number is determined for route modification requests.
+    enum TableType {
+        INTERFACE,  // Compute the table number based on the interface index.
+        LEGACY,  // Based on the UID; such tables can override the default network routes.
+        PRIVILEGED_LEGACY,  // Based on the UID; such tables can bypass VPNs.
+    };
+
     static const int ROUTE_TABLE_OFFSET_FROM_INDEX = 1000;
 
     static void Init();
@@ -34,8 +41,10 @@
     static bool addToDefaultNetwork(const char* interface, Permission permission);
     static bool removeFromDefaultNetwork(const char* interface, Permission permission);
 
-    static bool addRoute(const char* interface, const char* destination, const char* nexthop);
-    static bool removeRoute(const char* interface, const char* destination, const char* nexthop);
+    static bool addRoute(const char* interface, const char* destination, const char* nexthop,
+                         TableType tableType, unsigned uid);
+    static bool removeRoute(const char* interface, const char* destination, const char* nexthop,
+                            TableType tableType, unsigned uid);
 };
 
 #endif  // NETD_SERVER_ROUTE_CONTROLLER_H