Implement the "network route add/remove" API.

(cherry picked from commit 909c52248a4ad832548c5feb5e9a58323e30ab7b)

Change-Id: Idb09b0c22a3a519dda232995ced406e470ab9768
diff --git a/CommandListener.cpp b/CommandListener.cpp
index 34f356c..542e5f7 100644
--- a/CommandListener.cpp
+++ b/CommandListener.cpp
@@ -1754,8 +1754,35 @@
         return success(client);
     }
 
-    // network dns <add|remove> <netId> <num-resolvers> <resolver1> .. <resolverN> [searchDomain1] .. [searchDomainM]
-    // network route <add|remove> <other-route-params>
+    //    0      1     2       3         4            5           6
+    // network route  add   <netId> <interface> <destination> [nexthop]
+    // network route remove <netId> <interface> <destination> [nexthop]
+    if (!strcmp(argv[1], "route")) {
+        if (argc < 6 || argc > 7) {
+            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);
+        if (!sNetCtrl->isNetIdValid(netId)) {
+            return paramError(client, "Invalid netId");
+        }
+        const char* interface = argv[4];
+        const char* destination = argv[5];
+        const char* nexthop = argc == 7 ? argv[6] : NULL;
+        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 {
+            return syntaxError(client, "Unknown argument");
+        }
+        return success(client);
+    }
+
     // network legacy <uid> route <add|remove> <other-route-params>
     // network vpn create <netId> [owner_uid]
     // network vpn destroy <netId>
diff --git a/NetworkController.cpp b/NetworkController.cpp
index 14c63f3..eaaee5a 100644
--- a/NetworkController.cpp
+++ b/NetworkController.cpp
@@ -72,10 +72,8 @@
     // Add default network rules for the new netId.
     if (isNetIdValid(newNetId)) {
         Permission permission = mPermissionsController->getPermissionForNetwork(newNetId);
-
-        typedef std::multimap<unsigned, std::string>::const_iterator Iterator;
-        std::pair<Iterator, Iterator> range = mNetIdToInterfaces.equal_range(newNetId);
-        for (Iterator iter = range.first; iter != range.second; ++iter) {
+        InterfaceRange range = interfacesForNetId(newNetId, &status);
+        for (InterfaceIterator iter = range.first; iter != range.second; ++iter) {
             if (!mRouteController->addDefaultNetwork(iter->second.c_str(), permission)) {
                 status = false;
             }
@@ -85,10 +83,8 @@
     // Remove the old default network rules.
     if (isNetIdValid(oldNetId)) {
         Permission permission = mPermissionsController->getPermissionForNetwork(oldNetId);
-
-        typedef std::multimap<unsigned, std::string>::const_iterator Iterator;
-        std::pair<Iterator, Iterator> range = mNetIdToInterfaces.equal_range(oldNetId);
-        for (Iterator iter = range.first; iter != range.second; ++iter) {
+        InterfaceRange range = interfacesForNetId(oldNetId, &status);
+        for (InterfaceIterator iter = range.first; iter != range.second; ++iter) {
             if (!mRouteController->removeDefaultNetwork(iter->second.c_str(), permission)) {
                 status = false;
             }
@@ -175,12 +171,10 @@
         return false;
     }
 
-    typedef std::multimap<unsigned, std::string>::const_iterator Iterator;
-    for (Iterator iter = mNetIdToInterfaces.begin(); iter != mNetIdToInterfaces.end(); ++iter) {
-        if (iter->second == interface) {
-            ALOGE("interface %s already assigned to netId %u", interface, iter->first);
-            return false;
-        }
+    unsigned existingNetId = netIdForInterface(interface);
+    if (existingNetId != NETID_UNSET) {
+        ALOGE("interface %s already assigned to netId %u", interface, existingNetId);
+        return false;
     }
 
     if (!mRouteController->createNetwork(netId, interface, permission)) {
@@ -203,23 +197,20 @@
     bool status = true;
 
     Permission permission = mPermissionsController->getPermissionForNetwork(netId);
-
-    typedef std::multimap<unsigned, std::string>::const_iterator Iterator;
-    std::pair<Iterator, Iterator> range = mNetIdToInterfaces.equal_range(netId);
-    for (Iterator iter = range.first; iter != range.second; ++iter) {
+    InterfaceRange range = interfacesForNetId(netId, &status);
+    for (InterfaceIterator iter = range.first; iter != range.second; ++iter) {
         if (!mRouteController->destroyNetwork(netId, iter->second.c_str(), permission)) {
             status = false;
         }
     }
-
     mPermissionsController->setPermissionForNetwork(PERMISSION_NONE, netId);
     mNetIdToInterfaces.erase(netId);
 
     if (netId == getDefaultNetwork()) {
-        // Could the default network have changed from below us, after we evaluated the 'if', thus
-        // making it wrong to call setDefaultNetwork() now? No, because the default can only change
-        // due to another command from CommandListener, and those are serialized (i.e., they can't
-        // happen in parallel with the destroyNetwork() that we are currently processing).
+        // Could the default network have changed from below us, after we evaluated the 'if', making
+        // it wrong to call setDefaultNetwork() now? No, because the default can only change due to
+        // a command from CommandListener; but commands are serialized, I.e., we are processing the
+        // destroyNetwork() command here, so a setDefaultNetwork() command can't happen in parallel.
         setDefaultNetwork(NETID_UNSET);
     }
 
@@ -245,13 +236,7 @@
             continue;
         }
 
-        typedef std::multimap<unsigned, std::string>::const_iterator Iterator;
-        std::pair<Iterator, Iterator> range = mNetIdToInterfaces.equal_range(netId[i]);
-        if (range.first == range.second) {
-            ALOGE("unknown netId %u", netId[i]);
-            status = false;
-            continue;
-        }
+        InterfaceRange range = interfacesForNetId(netId[i], &status);
 
         Permission oldPermission = mPermissionsController->getPermissionForNetwork(netId[i]);
         if (oldPermission == newPermission) {
@@ -261,7 +246,7 @@
         // TODO: ioctl(SIOCKILLADDR, ...) to kill sockets on the network that don't have
         // newPermission.
 
-        for (Iterator iter = range.first; iter != range.second; ++iter) {
+        for (InterfaceIterator iter = range.first; iter != range.second; ++iter) {
             if (!mRouteController->modifyNetworkPermission(netId[i], iter->second.c_str(),
                                                            oldPermission, newPermission)) {
                 status = false;
@@ -274,6 +259,52 @@
     return status;
 }
 
+bool NetworkController::addRoute(unsigned netId, const char* interface, const char* destination,
+                                 const char* nexthop) {
+    return modifyRoute(netId, interface, destination, nexthop, true);
+}
+
+bool NetworkController::removeRoute(unsigned netId, const char* interface, const char* destination,
+                                    const char* nexthop) {
+    return modifyRoute(netId, interface, destination, nexthop, false);
+}
+
+unsigned NetworkController::netIdForInterface(const char* interface) {
+    for (InterfaceIterator iter = mNetIdToInterfaces.begin(); iter != mNetIdToInterfaces.end();
+         ++iter) {
+        if (iter->second == interface) {
+            return iter->first;
+        }
+    }
+    return NETID_UNSET;
+}
+
+NetworkController::InterfaceRange NetworkController::interfacesForNetId(unsigned netId,
+                                                                        bool* status) {
+    InterfaceRange range = mNetIdToInterfaces.equal_range(netId);
+    if (range.first == range.second) {
+        ALOGE("unknown netId %u", netId);
+        *status = false;
+    }
+    return range;
+}
+
+bool NetworkController::modifyRoute(unsigned netId, const char* interface, const char* destination,
+                                    const char* nexthop, bool add) {
+    if (!isNetIdValid(netId)) {
+        ALOGE("invalid netId %u", netId);
+        return false;
+    }
+
+    if (netIdForInterface(interface) != netId) {
+        ALOGE("netId %u has no such interface %s", netId, interface);
+        return false;
+    }
+
+    return add ? mRouteController->addRoute(interface, destination, nexthop) :
+                 mRouteController->removeRoute(interface, destination, nexthop);
+}
+
 NetworkController::UidEntry::UidEntry(
     int start, int end, unsigned netId, bool forward_dns)
       : uid_start(start),
diff --git a/NetworkController.h b/NetworkController.h
index e113bf5..d877fcc 100644
--- a/NetworkController.h
+++ b/NetworkController.h
@@ -71,7 +71,26 @@
     bool setPermissionForUser(Permission permission, const std::vector<unsigned>& uid);
     bool setPermissionForNetwork(Permission permission, const std::vector<unsigned>& netId);
 
+    // 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);
+    bool removeRoute(unsigned netId, const char* interface, const char* destination,
+                     const char* nexthop);
+
 private:
+    typedef std::multimap<unsigned, std::string>::const_iterator InterfaceIterator;
+    typedef std::pair<InterfaceIterator, InterfaceIterator> InterfaceRange;
+
+    // Returns the netId that |interface| belongs to, or NETID_UNSET if it doesn't belong to any.
+    unsigned netIdForInterface(const char* interface);
+
+    // Returns the interfaces assigned to |netId|. Sets |*status| to false if there are none.
+    InterfaceRange interfacesForNetId(unsigned netId, bool* status);
+
+    bool modifyRoute(unsigned netId, const char* interface, const char* destination,
+                     const char* nexthop, bool add);
+
     struct UidEntry {
         int uid_start;
         int uid_end;
diff --git a/RouteController.cpp b/RouteController.cpp
index a8f6700..ec82d1a 100644
--- a/RouteController.cpp
+++ b/RouteController.cpp
@@ -40,10 +40,10 @@
 
 bool runIpRuleCommand(const char* action, uint32_t priority, uint32_t table,
                       uint32_t fwmark, uint32_t mask, const char* interface) {
-
     char priorityString[UINT32_STRLEN];
-    char tableString[UINT32_STRLEN];
     snprintf(priorityString, sizeof(priorityString), "%u", priority);
+
+    char tableString[UINT32_STRLEN];
     snprintf(tableString, sizeof(tableString), "%u", table);
 
     char fwmarkString[sizeof("0x12345678/0x12345678")];
@@ -78,6 +78,32 @@
     return true;
 }
 
+bool runIpRouteCommand(const char* action, uint32_t table, const char* interface,
+                       const char* destination, const char* nexthop) {
+    char tableString[UINT32_STRLEN];
+    snprintf(tableString, sizeof(tableString), "%u", table);
+
+    int argc = 0;
+    const char* argv[16];
+
+    argv[argc++] = IP_PATH;
+    argv[argc++] = "route";
+    argv[argc++] = action;
+    argv[argc++] = "table";
+    argv[argc++] = tableString;
+    if (destination) {
+        argv[argc++] = destination;
+        argv[argc++] = "dev";
+        argv[argc++] = interface;
+        if (nexthop) {
+            argv[argc++] = "via";
+            argv[argc++] = nexthop;
+        }
+    }
+
+    return android_fork_execvp(argc, const_cast<char**>(argv), NULL, false, false);
+}
+
 bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission permission, bool add,
                            bool modifyIptables) {
     uint32_t table = getRouteTableForInterface(interface);
@@ -152,11 +178,16 @@
     uint32_t mask = getFwmarkMask(FWMARK_USE_NET_ID, !FWMARK_USE_EXPLICIT, !FWMARK_USE_PROTECT,
                                   permission);
 
-    if (!runIpRuleCommand(action, RULE_PRIORITY_DEFAULT_NETWORK, table, fwmark, mask, NULL)) {
+    return runIpRuleCommand(action, RULE_PRIORITY_DEFAULT_NETWORK, table, fwmark, mask, NULL);
+}
+
+bool modifyRoute(const char* interface, const char* destination, const char* nexthop, bool add) {
+    uint32_t table = getRouteTableForInterface(interface);
+    if (!table) {
         return false;
     }
 
-    return true;
+    return runIpRouteCommand(add ? ADD : DEL, table, interface, destination, nexthop);
 }
 
 }  // namespace
@@ -184,3 +215,13 @@
 bool RouteController::removeDefaultNetwork(const char* interface, Permission permission) {
     return modifyDefaultNetworkRules(interface, permission, DEL);
 }
+
+bool RouteController::addRoute(const char* interface, const char* destination,
+                               const char* nexthop) {
+    return modifyRoute(interface, destination, nexthop, true);
+}
+
+bool RouteController::removeRoute(const char* interface, const char* destination,
+                                  const char* nexthop) {
+    return modifyRoute(interface, destination, nexthop, false);
+}
diff --git a/RouteController.h b/RouteController.h
index 1db1da3..560d536 100644
--- a/RouteController.h
+++ b/RouteController.h
@@ -30,6 +30,9 @@
 
     static bool addDefaultNetwork(const char* interface, Permission permission);
     static bool removeDefaultNetwork(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);
 };
 
 #endif  // SYSTEM_NETD_ROUTE_CONTROLLER_H