#include <arpa/inet.h>
#include <errno.h>
+#include <linux/fib_rules.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <logwrap/logwrap.h>
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) {
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
// + 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;
}
}
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) },
{ &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,
return false;
}
- const char* action = add ? ADD : DEL;
+ uint16_t action = add ? RTM_NEWRULE : RTM_DELRULE;
Fwmark fwmark;
fwmark.permission = permission;
//
// 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;
}
// 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;
}
// 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;
}
// + 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;
}
}
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;
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: {
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.
//
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
}
}
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,