OSDN Git Service

Use netlink to add/delete rules as well as routes.
authorLorenzo Colitti <lorenzo@google.com>
Fri, 20 Jun 2014 14:03:29 +0000 (23:03 +0900)
committerLorenzo Colitti <lorenzo@google.com>
Thu, 26 Jun 2014 05:07:10 +0000 (14:07 +0900)
Also change the indentation of the rtattrs used in modifyIpRoute
to make it easier to see what attributes are being used and in
what sequence.

This change does not yet pass the errors back to CommandListener;
that is done in the next change in the series.

Change-Id: Ib2e174386c63cb0647d838d9c7d731cd6df39c4f

server/RouteController.cpp

index 980968f..9dbd021 100644 (file)
@@ -21,6 +21,7 @@
 
 #include <arpa/inet.h>
 #include <errno.h>
+#include <linux/fib_rules.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
 #include <logwrap/logwrap.h>
@@ -53,6 +54,9 @@ const uint32_t RULE_PRIORITY_UNREACHABLE           = 21000;
 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;
 
+const uint16_t kNetlinkRequestFlags = NLM_F_REQUEST | NLM_F_ACK;
+const uint16_t kNetlinkCreateRequestFlags = kNetlinkRequestFlags | NLM_F_CREATE | NLM_F_EXCL;
+
 std::map<std::string, uint32_t> interfaceToIndex;
 
 uint32_t getRouteTableForInterface(const char* interface) {
@@ -69,6 +73,49 @@ uint32_t getRouteTableForInterface(const char* interface) {
     return index ? index + RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX : 0;
 }
 
+// Sends a netlink request and expects an ack.
+// |iov| is an array of struct iovec that contains the netlink message payload.
+// The netlink header is generated by this function based on |action| and |flags|.
+// Returns -errno if there was an error or if the kernel reported an error.
+int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen) {
+    nlmsghdr nlmsg = {
+        .nlmsg_type = action,
+        .nlmsg_flags = flags,
+    };
+    iov[0].iov_base = &nlmsg;
+    iov[0].iov_len = sizeof(nlmsg);
+    for (int i = 0; i < iovlen; ++i) {
+        nlmsg.nlmsg_len += iov[i].iov_len;
+    }
+
+    int ret;
+    struct {
+        nlmsghdr msg;
+        nlmsgerr err;
+    } response;
+
+    sockaddr_nl kernel = {AF_NETLINK, 0, 0, 0};
+    int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+    if (sock != -1 &&
+            connect(sock, reinterpret_cast<sockaddr*>(&kernel), sizeof(kernel)) != -1 &&
+            writev(sock, iov, iovlen) != -1 &&
+            (ret = recv(sock, &response, sizeof(response), 0)) != -1) {
+        if (ret == sizeof(response)) {
+            ret = response.err.error;  // Netlink errors are negative errno.
+        } else {
+            ret = -EBADMSG;
+        }
+    } else {
+        ret = -errno;
+    }
+
+    if (sock != -1) {
+        close(sock);
+    }
+
+    return ret;
+}
+
 // 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
@@ -76,43 +123,56 @@ uint32_t getRouteTableForInterface(const char* interface) {
 // + 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.
-bool runIpRuleCommand(const char* action, uint32_t priority, uint32_t table, uint32_t fwmark,
-                      uint32_t mask, const char* interface) {
-    char priorityString[UINT32_STRLEN];
-    snprintf(priorityString, sizeof(priorityString), "%u", priority);
+bool modifyIpRule(uint16_t action, uint32_t priority, uint32_t table, uint32_t fwmark,
+                  uint32_t mask, const char* interface) {
+    // The interface name must include exactly one terminating NULL and be properly padded, or older
+    // kernels will refuse to delete rules.
+    uint8_t padding[RTA_ALIGNTO] = {0, 0, 0, 0};
+    uint16_t paddingLength = 0;
+    size_t interfaceLength = 0;
+    char oifname[IFNAMSIZ];
+    if (interface) {
+        interfaceLength = strlcpy(oifname, interface, IFNAMSIZ) + 1;
+        if (interfaceLength > IFNAMSIZ) {
+            return -ENAMETOOLONG;
+        }
+        paddingLength = RTA_SPACE(interfaceLength) - RTA_LENGTH(interfaceLength);
+    }
 
-    char tableString[UINT32_STRLEN];
-    snprintf(tableString, sizeof(tableString), "%u", table);
+    // Assemble a rule request and put it in an array of iovec structures.
+    fib_rule_hdr rule = {
+        .action = static_cast<uint8_t>(table ? FR_ACT_TO_TBL : FR_ACT_UNREACHABLE),
+    };
 
-    char fwmarkString[sizeof("0x12345678/0x12345678")];
-    snprintf(fwmarkString, sizeof(fwmarkString), "0x%x/0x%x", fwmark, mask);
+    rtattr fra_priority = { U16_RTA_LENGTH(sizeof(priority)),  FRA_PRIORITY };
+    rtattr fra_table    = { U16_RTA_LENGTH(sizeof(table)),     FRA_TABLE };
+    rtattr fra_fwmark   = { U16_RTA_LENGTH(sizeof(fwmark)),    FRA_FWMARK };
+    rtattr fra_fwmask   = { U16_RTA_LENGTH(sizeof(mask)),      FRA_FWMASK };
+    rtattr fra_oifname  = { U16_RTA_LENGTH(interfaceLength),   FRA_OIFNAME };
 
-    const char* version[] = {"-4", "-6"};
-    for (size_t i = 0; i < ARRAY_SIZE(version); ++i) {
-        int argc = 0;
-        const char* argv[16];
-
-        argv[argc++] = IP_PATH;
-        argv[argc++] = version[i];
-        argv[argc++] = "rule";
-        argv[argc++] = action;
-        argv[argc++] = "priority";
-        argv[argc++] = priorityString;
-        if (table) {
-            argv[argc++] = "table";
-            argv[argc++] = tableString;
-        } else {
-            argv[argc++] = "unreachable";
-        }
-        if (mask) {
-            argv[argc++] = "fwmark";
-            argv[argc++] = fwmarkString;
-        }
-        if (interface) {
-            argv[argc++] = "oif";
-            argv[argc++] = interface;
-        }
-        if (android_fork_execvp(argc, const_cast<char**>(argv), NULL, false, false)) {
+    iovec iov[] = {
+        { NULL,           0 },
+        { &rule,          sizeof(rule) },
+        { &fra_priority,  sizeof(fra_priority) },
+        { &priority,      sizeof(priority) },
+        { &fra_table,     table ? sizeof(fra_table) : 0 },
+        { &table,         table ? sizeof(table) : 0 },
+        { &fra_fwmark,    mask ? sizeof(fra_fwmark) : 0 },
+        { &fwmark,        mask ? sizeof(fwmark) : 0 },
+        { &fra_fwmask,    mask ? sizeof(fra_fwmask) : 0 },
+        { &mask,          mask ? sizeof(mask) : 0 },
+        { &fra_oifname,   interface ? sizeof(fra_oifname) : 0 },
+        { oifname,        interfaceLength },
+        { padding,        paddingLength },
+    };
+
+    uint16_t flags = (action == RTM_NEWRULE) ? kNetlinkCreateRequestFlags : kNetlinkRequestFlags;
+    uint8_t family[] = {AF_INET, AF_INET6};
+    for (size_t i = 0; i < ARRAY_SIZE(family); ++i) {
+        rule.family = family[i];
+        int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov));
+        if (ret) {
+            errno = -ret;
             return false;
         }
     }
@@ -157,27 +217,21 @@ int modifyIpRoute(uint16_t action, uint32_t table, const char* interface, const
         return -EINVAL;
     }
 
-    // Assemble a netlink request and put it in an array of iovec structures.
-    nlmsghdr nlmsg = {
-        .nlmsg_type = action,
-        .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
-    };
+    // Assemble a rtmsg and put it in an array of iovec structures.
     rtmsg rtmsg = {
         .rtm_protocol = RTPROT_STATIC,
         .rtm_type = RTN_UNICAST,
         .rtm_family = family,
         .rtm_dst_len = prefixLength,
     };
-    rtattr rta_table = { U16_RTA_LENGTH(sizeof(table)), RTA_TABLE };
-    rtattr rta_oif = { U16_RTA_LENGTH(sizeof(ifindex)), RTA_OIF };
-    rtattr rta_dst = { U16_RTA_LENGTH(rawLength), RTA_DST };
-    rtattr rta_gateway = { U16_RTA_LENGTH(rawLength), RTA_GATEWAY };
-    if (action == RTM_NEWROUTE) {
-        nlmsg.nlmsg_flags |= (NLM_F_CREATE | NLM_F_EXCL);
-    }
+
+    rtattr rta_table   = { U16_RTA_LENGTH(sizeof(table)),    RTA_TABLE };
+    rtattr rta_oif     = { U16_RTA_LENGTH(sizeof(ifindex)),  RTA_OIF };
+    rtattr rta_dst     = { U16_RTA_LENGTH(rawLength),        RTA_DST };
+    rtattr rta_gateway = { U16_RTA_LENGTH(rawLength),        RTA_GATEWAY };
 
     iovec iov[] = {
-        { &nlmsg,        sizeof(nlmsg) },
+        { NULL,          0 },
         { &rtmsg,        sizeof(rtmsg) },
         { &rta_table,    sizeof(rta_table) },
         { &table,        sizeof(table) },
@@ -188,38 +242,9 @@ int modifyIpRoute(uint16_t action, uint32_t table, const char* interface, const
         { &rta_gateway,  nexthop ? sizeof(rta_gateway) : 0 },
         { rawNexthop,    nexthop ? static_cast<size_t>(rawLength) : 0 },
     };
-    int iovlen = ARRAY_SIZE(iov);
-
-    for (int i = 0; i < iovlen; ++i) {
-        nlmsg.nlmsg_len += iov[i].iov_len;
-    }
-
-    int ret;
-    struct {
-        nlmsghdr msg;
-        nlmsgerr err;
-    } response;
 
-    sockaddr_nl kernel = {AF_NETLINK, 0, 0, 0};
-    int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
-    if (sock != -1 &&
-            connect(sock, reinterpret_cast<sockaddr *>(&kernel), sizeof(kernel)) != -1 &&
-            writev(sock, iov, iovlen) != -1 &&
-            (ret = recv(sock, &response, sizeof(response), 0)) != -1) {
-        if (ret == sizeof(response)) {
-            ret = response.err.error;  // Netlink errors are negative errno.
-        } else {
-            ret = -EBADMSG;
-        }
-    } else {
-        ret = -errno;
-    }
-
-    if (sock != -1) {
-        close(sock);
-    }
-
-    return ret;
+    uint16_t flags = (action == RTM_NEWROUTE) ? kNetlinkCreateRequestFlags : kNetlinkRequestFlags;
+    return sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov));
 }
 
 bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission permission, bool add,
@@ -229,7 +254,7 @@ bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission per
         return false;
     }
 
-    const char* action = add ? ADD : DEL;
+    uint16_t action = add ? RTM_NEWRULE : RTM_DELRULE;
 
     Fwmark fwmark;
     fwmark.permission = permission;
@@ -241,8 +266,8 @@ bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission per
     //
     // Supports apps that use SO_BINDTODEVICE or IP_PKTINFO options and the kernel that already
     // knows the outgoing interface (typically for link-local communications).
-    if (!runIpRuleCommand(action, RULE_PRIORITY_PER_NETWORK_INTERFACE, table, fwmark.intValue,
-                          mask.intValue, interface)) {
+    if (!modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_INTERFACE, table, fwmark.intValue,
+                      mask.intValue, interface)) {
         return false;
     }
 
@@ -253,8 +278,8 @@ bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission per
     // network stay on that network even if the default network changes.
     fwmark.netId = netId;
     mask.netId = FWMARK_NET_ID_MASK;
-    if (!runIpRuleCommand(action, RULE_PRIORITY_PER_NETWORK_NORMAL, table, fwmark.intValue,
-                          mask.intValue, NULL)) {
+    if (!modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_NORMAL, table, fwmark.intValue,
+                      mask.intValue, NULL)) {
         return false;
     }
 
@@ -266,8 +291,8 @@ bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission per
     // checked at the time the netId was set into the fwmark, but we do so to be consistent.
     fwmark.explicitlySelected = true;
     mask.explicitlySelected = true;
-    if (!runIpRuleCommand(action, RULE_PRIORITY_PER_NETWORK_EXPLICIT, table, fwmark.intValue,
-                          mask.intValue, NULL)) {
+    if (!modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_EXPLICIT, table, fwmark.intValue,
+                      mask.intValue, NULL)) {
         return false;
     }
 
@@ -279,11 +304,11 @@ bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission per
     // + Mark sockets that accept connections from this interface so that the connection stays on
     //   the same interface.
     if (modifyIptables) {
-        action = add ? "-A" : "-D";
+        const char* iptablesAction = add ? "-A" : "-D";
         char markString[UINT32_HEX_STRLEN];
         snprintf(markString, sizeof(markString), "0x%x", netId);
-        if (execIptables(V4V6, "-t", "mangle", action, "INPUT", "-i", interface, "-j", "MARK",
-                         "--set-mark", markString, NULL)) {
+        if (execIptables(V4V6, "-t", "mangle", iptablesAction, "INPUT", "-i", interface,
+                         "-j", "MARK", "--set-mark", markString, NULL)) {
             return false;
         }
     }
@@ -291,7 +316,7 @@ bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission per
     return true;
 }
 
-bool modifyDefaultNetworkRules(const char* interface, Permission permission, const char* action) {
+bool modifyDefaultNetworkRules(const char* interface, Permission permission, uint16_t action) {
     uint32_t table = getRouteTableForInterface(interface);
     if (!table) {
         return false;
@@ -305,15 +330,15 @@ bool modifyDefaultNetworkRules(const char* interface, Permission permission, con
     mask.netId = FWMARK_NET_ID_MASK;
     mask.permission = permission;
 
-    return runIpRuleCommand(action, RULE_PRIORITY_DEFAULT_NETWORK, table, fwmark.intValue,
-                            mask.intValue, NULL);
+    return modifyIpRule(action, RULE_PRIORITY_DEFAULT_NETWORK, table, fwmark.intValue,
+                        mask.intValue, NULL);
 }
 
 // Adds or removes an IPv4 or IPv6 route to the specified table and, if it's directly-connected
 // route, to the main table as well.
 // Returns 0 on success or negative errno on failure.
 int modifyRoute(const char* interface, const char* destination, const char* nexthop,
-                int action, RouteController::TableType tableType, unsigned /* uid */) {
+                uint16_t action, RouteController::TableType tableType, unsigned /* uid */) {
     uint32_t table = 0;
     switch (tableType) {
         case RouteController::INTERFACE: {
@@ -399,7 +424,8 @@ void RouteController::Init() {
     Fwmark mask;
     mask.netId = FWMARK_NET_ID_MASK;
 
-    runIpRuleCommand(ADD, RULE_PRIORITY_MAIN, RT_TABLE_MAIN, fwmark.intValue, mask.intValue, NULL);
+    modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_MAIN, RT_TABLE_MAIN, fwmark.intValue, mask.intValue,
+                 NULL);
 
     // Add rules to allow lookup of legacy routes.
     //
@@ -411,20 +437,20 @@ void RouteController::Init() {
     fwmark.explicitlySelected = false;
     mask.explicitlySelected = true;
 
-    runIpRuleCommand(ADD, RULE_PRIORITY_LEGACY, ROUTE_TABLE_LEGACY, fwmark.intValue, mask.intValue,
-                     NULL);
+    modifyIpRule(RTM_NEWRULE, 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);
+    modifyIpRule(RTM_NEWRULE, 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
     // that are already marked with a specific NetId don't fall-through to the main table.
-    runIpRuleCommand(ADD, RULE_PRIORITY_UNREACHABLE, 0, 0, 0, NULL);
+    modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_UNREACHABLE, 0, 0, 0, NULL);
 #endif
 }
 
@@ -447,11 +473,11 @@ bool RouteController::modifyNetworkPermission(unsigned netId, const char* interf
 }
 
 bool RouteController::addToDefaultNetwork(const char* interface, Permission permission) {
-    return modifyDefaultNetworkRules(interface, permission, ADD);
+    return modifyDefaultNetworkRules(interface, permission, RTM_NEWRULE);
 }
 
 bool RouteController::removeFromDefaultNetwork(const char* interface, Permission permission) {
-    return modifyDefaultNetworkRules(interface, permission, DEL);
+    return modifyDefaultNetworkRules(interface, permission, RTM_DELRULE);
 }
 
 int RouteController::addRoute(const char* interface, const char* destination,