Use netlink to add/delete rules as well as routes.
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
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index 980968f..9dbd021 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -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 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 @@
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 @@
// + 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);
-
- char tableString[UINT32_STRLEN];
- snprintf(tableString, sizeof(tableString), "%u", table);
-
- char fwmarkString[sizeof("0x12345678/0x12345678")];
- snprintf(fwmarkString, sizeof(fwmarkString), "0x%x/0x%x", fwmark, mask);
-
- 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";
+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;
}
- 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)) {
+ paddingLength = RTA_SPACE(interfaceLength) - RTA_LENGTH(interfaceLength);
+ }
+
+ // 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),
+ };
+
+ 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 };
+
+ 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 @@
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 @@
{ &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 @@
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 @@
//
// 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 @@
// 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 @@
// 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 @@
// + 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 @@
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 @@
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 @@
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 @@
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::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,