Use our netlink code to flush routes as well.

Most of the CL is refactoring the rule flush code to be more
generic and move it and various callback definitions to
NetlinkCommands. After that, flushing routes is very simple.

Test: bullhead builds, boots
Test: netd_{unit,integration}_test pass
Bug: 34873832
Change-Id: I0613d525f043d0a8b234a89982281b909011c7e5
diff --git a/server/NetlinkCommands.cpp b/server/NetlinkCommands.cpp
index 3bee73b..1380196 100644
--- a/server/NetlinkCommands.cpp
+++ b/server/NetlinkCommands.cpp
@@ -74,7 +74,7 @@
 #endif
 #endif
 WARN_UNUSED_RESULT int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen,
-                                          const NetlinkDumpCallback& callback) {
+                                          const NetlinkDumpCallback *callback) {
     nlmsghdr nlmsg = {
         .nlmsg_type = action,
         .nlmsg_flags = flags,
@@ -90,7 +90,6 @@
         return sock;
     }
 
-
     int ret = 0;
 
     if (writev(sock, iov, iovlen) == -1) {
@@ -102,10 +101,8 @@
 
     if (flags & NLM_F_ACK) {
         ret = recvNetlinkAck(sock);
-    }
-
-    if ((flags & NLM_F_DUMP) && callback != nullptr) {
-        ret = processNetlinkDump(sock, callback);
+    } else if ((flags & NLM_F_DUMP) && callback != nullptr) {
+        ret = processNetlinkDump(sock, *callback);
     }
 
     close(sock);
@@ -148,5 +145,72 @@
     return 0;
 }
 
+WARN_UNUSED_RESULT int rtNetlinkFlush(uint16_t getAction, uint16_t deleteAction,
+                                     const char *what, const NetlinkDumpFilter& shouldDelete) {
+    // RTM_GETxxx is always RTM_DELxxx + 1, see <linux/rtnetlink.h>.
+    if (getAction != deleteAction + 1) {
+        ALOGE("Unknown flush type getAction=%d deleteAction=%d", getAction, deleteAction);
+        return -EINVAL;
+    }
+
+    int writeSock = openRtNetlinkSocket();
+    if (writeSock < 0) {
+        return writeSock;
+    }
+
+    NetlinkDumpCallback callback = [writeSock, deleteAction, shouldDelete, what] (nlmsghdr *nlh) {
+        if (!shouldDelete(nlh)) return;
+
+        nlh->nlmsg_type = deleteAction;
+        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+        if (write(writeSock, nlh, nlh->nlmsg_len) == -1) {
+            ALOGE("Error writing flush request: %s", strerror(errno));
+            return;
+        }
+
+        int ret = recvNetlinkAck(writeSock);
+        // 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 ignore ENOENT.
+        if (ret != 0 && ret != -ENOENT) {
+            ALOGW("Flushing %s: %s", what, strerror(-ret));
+        }
+    };
+
+    int ret = 0;
+    for (const int family : { AF_INET, AF_INET6 }) {
+        // struct fib_rule_hdr and struct rtmsg are functionally identical.
+        rtmsg rule = {
+            .rtm_family = static_cast<uint8_t>(family),
+        };
+        iovec iov[] = {
+            { NULL,  0 },
+            { &rule, sizeof(rule) },
+        };
+        uint16_t flags = NETLINK_DUMP_FLAGS;
+
+        if ((ret = sendNetlinkRequest(getAction, flags, iov, ARRAY_SIZE(iov), &callback)) != 0) {
+            break;
+        }
+    }
+
+    close(writeSock);
+
+    return ret;
+}
+
+uint32_t getRtmU32Attribute(const nlmsghdr *nlh, int attribute) {
+    uint32_t rta_len = RTM_PAYLOAD(nlh);
+    rtmsg *msg = reinterpret_cast<rtmsg *>(NLMSG_DATA(nlh));
+    rtattr *rta = reinterpret_cast<rtattr *> RTM_RTA(msg);
+    for (; RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) {
+        if (rta->rta_type == attribute) {
+            return *(static_cast<uint32_t *>(RTA_DATA(rta)));
+        }
+    }
+    return 0;
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/server/NetlinkCommands.h b/server/NetlinkCommands.h
index 0b109e1..2db7c16 100644
--- a/server/NetlinkCommands.h
+++ b/server/NetlinkCommands.h
@@ -18,6 +18,8 @@
 #define NETD_SERVER_NETLINK_UTIL_H
 
 #include <functional>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
 
 #include "NetdConstants.h"
 
@@ -26,9 +28,14 @@
 
 const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
 
+const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
+const uint16_t NETLINK_CREATE_REQUEST_FLAGS = NETLINK_REQUEST_FLAGS | NLM_F_CREATE | NLM_F_EXCL;
+const uint16_t NETLINK_DUMP_FLAGS = NLM_F_REQUEST | NLM_F_DUMP;
+
 // Generic code for processing netlink dumps.
 const int kNetlinkDumpBufferSize = 8192;
 typedef std::function<void(nlmsghdr *)> NetlinkDumpCallback;
+typedef std::function<bool(nlmsghdr *)> NetlinkDumpFilter;
 
 // Opens an RTNetlink socket and connects it to the kernel.
 WARN_UNUSED_RESULT int openRtNetlinkSocket();
@@ -40,7 +47,6 @@
 // Sends a netlink request and possibly expects an ACK. The first element of iov should be null and
 // will be set to the netlink message headerheader. The subsequent elements are the contents of the
 // request.
-WARN_UNUSED_RESULT int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen);
 
 // Disable optimizations in ASan build.
 // ASan reports an out-of-bounds 32-bit(!) access in the first loop of the
@@ -51,11 +57,21 @@
 #endif
 #endif
 WARN_UNUSED_RESULT int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen,
-                                          const NetlinkDumpCallback& callback);
+                                          const NetlinkDumpCallback *callback);
 
 // Processes a netlink dump, passing every message to the specified |callback|.
 WARN_UNUSED_RESULT int processNetlinkDump(int sock, const NetlinkDumpCallback& callback);
 
+// Flushes netlink objects that take an rtmsg structure (FIB rules, routes...). |getAction| and
+// |deleteAction| specify the netlink message types, e.g., RTM_GETRULE and RTM_DELRULE.
+// |shouldDelete| specifies whether a given object should be deleted or not. |what| is a
+// human-readable name for the objects being flushed, e.g. "rules".
+WARN_UNUSED_RESULT int rtNetlinkFlush(uint16_t getAction, uint16_t deleteAction,
+                                      const char *what, const NetlinkDumpFilter& shouldDelete);
+
+// Returns the value of the specific __u32 attribute, or 0 if the attribute was not present.
+uint32_t getRtmU32Attribute(const nlmsghdr *nlh, int attribute);
+
 }  // namespace net
 }  // namespace android
 
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index 87b0043..e1a0e8d 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -92,14 +92,8 @@
         __u32           end;
 };
 
-const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
-const uint16_t NETLINK_CREATE_REQUEST_FLAGS = NETLINK_REQUEST_FLAGS | NLM_F_CREATE | NLM_F_EXCL;
-const uint16_t NETLINK_DUMP_FLAGS = NLM_F_REQUEST | NLM_F_DUMP;
-
 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_LOOPBACK = "lo";
 const char* const IIF_NONE = NULL;
@@ -111,8 +105,6 @@
 const char* const RT_TABLES_PATH = "/data/misc/net/rt_tables";
 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) {
@@ -311,7 +303,7 @@
     uint16_t flags = (action == RTM_NEWRULE) ? NETLINK_CREATE_REQUEST_FLAGS : NETLINK_REQUEST_FLAGS;
     for (size_t i = 0; i < ARRAY_SIZE(AF_FAMILIES); ++i) {
         rule.family = AF_FAMILIES[i];
-        if (int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov))) {
+        if (int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov), nullptr)) {
             if (!(action == RTM_DELRULE && ret == -ENOENT && priority == RULE_PRIORITY_TETHERING)) {
                 // Don't log when deleting a tethering rule that's not there. This matches the
                 // behaviour of clearTetheringRules, which ignores ENOENT in this case.
@@ -423,7 +415,7 @@
 
     uint16_t flags = (action == RTM_NEWROUTE) ? NETLINK_CREATE_REQUEST_FLAGS :
                                                 NETLINK_REQUEST_FLAGS;
-    int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov));
+    int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov), nullptr);
     if (ret) {
         ALOGE("Error %s route %s -> %s %s to table %u: %s",
               actionName(action), destination, nexthop, interface, table, strerror(-ret));
@@ -842,72 +834,6 @@
                         inputInterface, OIF_NONE, INVALID_UID, INVALID_UID);
 }
 
-uint32_t getRulePriority(const nlmsghdr *nlh) {
-    uint32_t rta_len = RTM_PAYLOAD(nlh);
-    rtmsg *msg = reinterpret_cast<rtmsg *>(NLMSG_DATA(nlh));
-    rtattr *rta = reinterpret_cast<rtattr *> RTM_RTA(msg);
-    for (; RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) {
-        if (rta->rta_type == FRA_PRIORITY) {
-            return *(static_cast<uint32_t *>(RTA_DATA(rta)));
-        }
-    }
-    return 0;
-}
-
-// Returns 0 on success or negative errno on failure.
-WARN_UNUSED_RESULT int flushRules() {
-    int readSock = openRtNetlinkSocket();
-    if (readSock < 0) {
-        return readSock;
-    }
-
-    int writeSock = openRtNetlinkSocket();
-    if (writeSock < 0) {
-        close(readSock);
-        return writeSock;
-    }
-
-    NetlinkDumpCallback callback = [writeSock] (nlmsghdr *nlh) {
-        // Don't touch rules at priority 0 because by default they are used for local input.
-        if (getRulePriority(nlh) == 0) return;
-
-        nlh->nlmsg_type = RTM_DELRULE;
-        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
-        if (write(writeSock, nlh, nlh->nlmsg_len) == -1) {
-            ALOGE("Error writing flush request: %s", strerror(errno));
-            return;
-        }
-
-        int ret = recvNetlinkAck(writeSock);
-        if (ret != 0 && ret != -ENOENT) {
-            ALOGW("Flushing rules: %s", strerror(-ret));
-        }
-    };
-
-    int ret = 0;
-    for (const int family : { AF_INET, AF_INET6 }) {
-        // struct fib_rule_hdr and struct rtmsg are functionally identical.
-        rtmsg rule = {
-            .rtm_family = static_cast<uint8_t>(family),
-        };
-        iovec iov[] = {
-            { NULL,  0 },
-            { &rule, sizeof(rule) },
-        };
-        uint16_t action = RTM_GETRULE;
-        uint16_t flags = NETLINK_DUMP_FLAGS;
-
-        if ((ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov), callback)) != 0) {
-            break;
-        }
-    }
-
-    close(readSock);
-    close(writeSock);
-
-    return ret;
-}
-
 // Adds or removes an IPv4 or IPv6 route to the specified table and, if it's a directly-connected
 // route, to the main table as well.
 // Returns 0 on success or negative errno on failure.
@@ -945,56 +871,6 @@
     return 0;
 }
 
-// Returns 0 on success or negative errno on failure.
-WARN_UNUSED_RESULT int flushRoutes(const char* interface) {
-    uint32_t table = getRouteTableForInterface(interface);
-    if (table == RT_TABLE_UNSPEC) {
-        return -ESRCH;
-    }
-
-    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,
-            IP_VERSIONS[i],
-            "route",
-            "flush",
-            "table",
-            tableString,
-        };
-
-        // 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;
-        }
-    }
-
-    // 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;
-}
-
 WARN_UNUSED_RESULT int clearTetheringRules(const char* inputInterface) {
     int ret = 0;
     while (ret == 0) {
@@ -1009,6 +885,48 @@
     }
 }
 
+uint32_t getRulePriority(const nlmsghdr *nlh) {
+    return getRtmU32Attribute(nlh, FRA_PRIORITY);
+}
+
+uint32_t getRouteTable(const nlmsghdr *nlh) {
+    return getRtmU32Attribute(nlh, RTA_TABLE);
+}
+
+WARN_UNUSED_RESULT int flushRules() {
+    NetlinkDumpFilter shouldDelete = [] (nlmsghdr *nlh) {
+        // Don't touch rules at priority 0 because by default they are used for local input.
+        return getRulePriority(nlh) != 0;
+    };
+    return rtNetlinkFlush(RTM_GETRULE, RTM_DELRULE, "rules", shouldDelete);
+}
+
+WARN_UNUSED_RESULT int flushRoutes(uint32_t table) {
+    NetlinkDumpFilter shouldDelete = [table] (nlmsghdr *nlh) {
+        return getRouteTable(nlh) == table;
+    };
+
+    return rtNetlinkFlush(RTM_GETROUTE, RTM_DELROUTE, "routes", shouldDelete);
+}
+
+// Returns 0 on success or negative errno on failure.
+WARN_UNUSED_RESULT int flushRoutes(const char* interface) {
+    uint32_t table = getRouteTableForInterface(interface);
+    if (table == RT_TABLE_UNSPEC) {
+        return -ESRCH;
+    }
+
+    int ret = flushRoutes(table);
+
+    // If we failed to flush routes, the caller may elect to keep this interface around, so keep
+    // track of its name.
+    if (ret == 0) {
+        interfaceToTable.erase(interface);
+    }
+
+    return ret;
+}
+
 }  // namespace
 
 int RouteController::Init(unsigned localNetId) {