#include "RouteController.h"
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fib_rules.h>
+#include <net/if.h>
+#include <sys/stat.h>
+
+#include <private/android_filesystem_config.h>
+
+#include <map>
+
#include "Fwmark.h"
#include "UidRanges.h"
+#include "DummyNetwork.h"
+#include "android-base/file.h"
#define LOG_TAG "Netd"
#include "log/log.h"
#include "logwrap/logwrap.h"
+#include "netutils/ifc.h"
#include "resolv_netid.h"
-#include <arpa/inet.h>
-#include <fcntl.h>
-#include <linux/fib_rules.h>
-#include <map>
-#include <net/if.h>
-#include <sys/stat.h>
+using android::base::WriteStringToFile;
namespace {
// BEGIN CONSTANTS --------------------------------------------------------------------------------
const uint32_t RULE_PRIORITY_VPN_OVERRIDE_SYSTEM = 10000;
+const uint32_t RULE_PRIORITY_VPN_OVERRIDE_OIF = 10500;
const uint32_t RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL = 11000;
const uint32_t RULE_PRIORITY_SECURE_VPN = 12000;
+const uint32_t RULE_PRIORITY_PROHIBIT_NON_VPN = 12500;
const uint32_t RULE_PRIORITY_EXPLICIT_NETWORK = 13000;
const uint32_t RULE_PRIORITY_OUTPUT_INTERFACE = 14000;
const uint32_t RULE_PRIORITY_LEGACY_SYSTEM = 15000;
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;
const char* const OIF_NONE = NULL;
const bool ACTION_ADD = true;
const bool MODIFY_NON_UID_BASED_RULES = true;
const char* const RT_TABLES_PATH = "/data/misc/net/rt_tables";
-const int RT_TABLES_FLAGS = O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW | O_CLOEXEC;
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;
addTableName(entry.second, entry.first, &contents);
}
- int fd = open(RT_TABLES_PATH, RT_TABLES_FLAGS, RT_TABLES_MODE);
- if (fd == -1) {
- ALOGE("failed to create %s (%s)", RT_TABLES_PATH, strerror(errno));
+ if (!WriteStringToFile(contents, RT_TABLES_PATH, RT_TABLES_MODE, AID_SYSTEM, AID_WIFI)) {
+ ALOGE("failed to write to %s (%s)", RT_TABLES_PATH, strerror(errno));
return;
}
- // File creation is affected by umask, so make sure the right mode bits are set.
- if (fchmod(fd, RT_TABLES_MODE) == -1) {
- ALOGE("failed to set mode 0%o on %s (%s)", RT_TABLES_MODE, RT_TABLES_PATH, strerror(errno));
- }
- ssize_t bytesWritten = write(fd, contents.data(), contents.size());
- if (bytesWritten != static_cast<ssize_t>(contents.size())) {
- ALOGE("failed to write to %s (%zd vs %zu bytes) (%s)", RT_TABLES_PATH, bytesWritten,
- contents.size(), strerror(errno));
- }
- close(fd);
}
// 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.
+
+// Disable optimizations in ASan build.
+// ASan reports an out-of-bounds 32-bit(!) access in the first loop of the
+// function (over iov[]).
+#ifdef __clang__
+#if __has_feature(address_sanitizer)
+__attribute__((optnone))
+#endif
+#endif
WARN_UNUSED_RESULT int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen) {
nlmsghdr nlmsg = {
.nlmsg_type = action,
nlmsgerr err;
} response;
- int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+ int sock = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
if (sock != -1 &&
connect(sock, reinterpret_cast<const sockaddr*>(&NETLINK_ADDRESS),
sizeof(NETLINK_ADDRESS)) != -1 &&
// 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
-// returns ENETUNREACH.
+// + If |table| is non-zero, the rule points at the specified routing table. Otherwise, the table is
+// unspecified. An unspecified table is not allowed when creating an FR_ACT_TO_TBL rule.
// + If |mask| is non-zero, the rule matches the specified fwmark and mask. Otherwise, |fwmark| is
// ignored.
// + If |iif| is non-NULL, the rule matches the specified incoming interface.
// range (inclusive). Otherwise, the rule matches packets from all UIDs.
//
// Returns 0 on success or negative errno on failure.
-WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table,
- uint32_t fwmark, uint32_t mask, const char* iif,
+WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint8_t ruleType,
+ uint32_t table, uint32_t fwmark, uint32_t mask, const char* iif,
const char* oif, uid_t uidStart, uid_t uidEnd) {
// Ensure that if you set a bit in the fwmark, it's not being ignored by the mask.
if (fwmark & ~mask) {
ALOGE("incompatible start and end UIDs (%u vs %u)", uidStart, uidEnd);
return -EUSERS;
}
+
bool isUidRule = (uidStart != INVALID_UID);
// Assemble a rule request and put it in an array of iovec structures.
fib_rule_hdr rule = {
- .action = static_cast<uint8_t>(table != RT_TABLE_UNSPEC ? FR_ACT_TO_TBL :
- FR_ACT_UNREACHABLE),
+ .action = ruleType,
+ // Note that here we're implicitly setting rule.table to 0. When we want to specify a
+ // non-zero table, we do this via the FRATTR_TABLE attribute.
};
+ // Don't ever create a rule that looks up table 0, because table 0 is the local table.
+ // It's OK to specify a table ID of 0 when deleting a rule, because that doesn't actually select
+ // table 0, it's a wildcard that matches anything.
+ if (table == RT_TABLE_UNSPEC && rule.action == FR_ACT_TO_TBL && action != RTM_DELRULE) {
+ ALOGE("RT_TABLE_UNSPEC only allowed when deleting rules");
+ return -ENOTUNIQ;
+ }
+
rtattr fraIifName = { U16_RTA_LENGTH(iifLength), FRA_IIFNAME };
rtattr fraOifName = { U16_RTA_LENGTH(oifLength), FRA_OIFNAME };
}
WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table,
+ uint32_t fwmark, uint32_t mask, const char* iif,
+ const char* oif, uid_t uidStart, uid_t uidEnd) {
+ return modifyIpRule(action, priority, FR_ACT_TO_TBL, table, fwmark, mask, iif, oif, uidStart,
+ uidEnd);
+}
+
+WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table,
uint32_t fwmark, uint32_t mask) {
return modifyIpRule(action, priority, table, fwmark, mask, IIF_NONE, OIF_NONE, INVALID_UID,
INVALID_UID);
}
return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, priority, table, fwmark.intValue,
- mask.intValue, IIF_NONE, OIF_NONE, uidStart, uidEnd);
+ mask.intValue, IIF_LOOPBACK, OIF_NONE, uidStart, uidEnd);
}
// A rule to allow system apps to send traffic over this VPN even if they are not part of the target
//
// Supports apps that use SO_BINDTODEVICE or IP_PKTINFO options and the kernel that already knows
// the outgoing interface (typically for link-local communications).
-WARN_UNUSED_RESULT int modifyOutputInterfaceRule(const char* interface, uint32_t table,
- Permission permission, uid_t uidStart,
- uid_t uidEnd, bool add) {
+WARN_UNUSED_RESULT int modifyOutputInterfaceRules(const char* interface, uint32_t table,
+ Permission permission, uid_t uidStart,
+ uid_t uidEnd, bool add) {
Fwmark fwmark;
Fwmark mask;
fwmark.permission = permission;
mask.permission = permission;
+ // If this rule does not specify a UID range, then also add a corresponding high-priority rule
+ // for UID. This covers forwarded packets and system daemons such as the tethering DHCP server.
+ if (uidStart == INVALID_UID && uidEnd == INVALID_UID) {
+ if (int ret = modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_VPN_OVERRIDE_OIF,
+ table, fwmark.intValue, mask.intValue, IIF_NONE, interface,
+ UID_ROOT, UID_ROOT)) {
+ return ret;
+ }
+ }
+
return modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE, RULE_PRIORITY_OUTPUT_INTERFACE, table,
fwmark.intValue, mask.intValue, IIF_NONE, interface, uidStart, uidEnd);
}
fwmark.intValue, mask.intValue);
}
+int configureDummyNetwork() {
+ const char *interface = DummyNetwork::INTERFACE_NAME;
+ uint32_t table = getRouteTableForInterface(interface);
+ if (table == RT_TABLE_UNSPEC) {
+ // getRouteTableForInterface has already looged an error.
+ return -ESRCH;
+ }
+
+ ifc_init();
+ int ret = ifc_up(interface);
+ ifc_close();
+ if (ret) {
+ ALOGE("Can't bring up %s: %s", interface, strerror(errno));
+ return -errno;
+ }
+
+ if ((ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE,
+ INVALID_UID, INVALID_UID, ACTION_ADD))) {
+ ALOGE("Can't create oif rules for %s: %s", interface, strerror(-ret));
+ return ret;
+ }
+
+ if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "0.0.0.0/0", NULL))) {
+ ALOGE("Can't add IPv4 default route to %s: %s", interface, strerror(-ret));
+ return ret;
+ }
+
+ if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "::/0", NULL))) {
+ ALOGE("Can't add IPv6 default route to %s: %s", interface, strerror(-ret));
+ return ret;
+ }
+
+ return 0;
+}
+
// Add a new rule to look up the 'main' table, with the same selectors as the "default network"
// rule, but with a lower priority. We will never create routes in the main table; it should only be
// used for directly-connected routes implicitly created by the kernel when adding IP addresses.
if (int ret = modifyIncomingPacketMark(netId, interface, PERMISSION_NONE, add)) {
return ret;
}
- return modifyOutputInterfaceRule(interface, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE,
- INVALID_UID, INVALID_UID, add);
+ return modifyOutputInterfaceRules(interface, ROUTE_TABLE_LOCAL_NETWORK, PERMISSION_NONE,
+ INVALID_UID, INVALID_UID, add);
}
WARN_UNUSED_RESULT int modifyPhysicalNetwork(unsigned netId, const char* interface,
add)) {
return ret;
}
- if (int ret = modifyOutputInterfaceRule(interface, table, permission, INVALID_UID, INVALID_UID,
+ if (int ret = modifyOutputInterfaceRules(interface, table, permission, INVALID_UID, INVALID_UID,
add)) {
return ret;
}
return modifyImplicitNetworkRule(netId, table, permission, add);
}
+WARN_UNUSED_RESULT int modifyRejectNonSecureNetworkRule(const UidRanges& uidRanges, bool add) {
+ Fwmark fwmark;
+ Fwmark mask;
+ fwmark.protectedFromVpn = false;
+ mask.protectedFromVpn = true;
+
+ for (const UidRanges::Range& range : uidRanges.getRanges()) {
+ if (int ret = modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE,
+ RULE_PRIORITY_PROHIBIT_NON_VPN, FR_ACT_PROHIBIT, RT_TABLE_UNSPEC,
+ fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE,
+ range.first, range.second)) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
WARN_UNUSED_RESULT int modifyVirtualNetwork(unsigned netId, const char* interface,
const UidRanges& uidRanges, bool secure, bool add,
bool modifyNonUidBasedRules) {
range.second, add)) {
return ret;
}
- if (int ret = modifyOutputInterfaceRule(interface, table, PERMISSION_NONE, range.first,
- range.second, add)) {
+ if (int ret = modifyOutputInterfaceRules(interface, table, PERMISSION_NONE, range.first,
+ range.second, add)) {
return ret;
}
}
}
int ret = modifyIpRoute(action, table, interface, destination, nexthop);
- // We allow apps to call requestRouteToHost() multiple times with the same route, so ignore
- // EEXIST failures when adding routes to legacy tables.
- if (ret && !(action == RTM_NEWROUTE && ret == -EEXIST &&
- (tableType == RouteController::LEGACY_NETWORK ||
- tableType == RouteController::LEGACY_SYSTEM))) {
+ // Trying to add a route that already exists shouldn't cause an error.
+ if (ret && !(action == RTM_NEWROUTE && ret == -EEXIST)) {
return ret;
}
return ret;
}
+WARN_UNUSED_RESULT int clearTetheringRules(const char* inputInterface) {
+ int ret = 0;
+ while (ret == 0) {
+ ret = modifyIpRule(RTM_DELRULE, RULE_PRIORITY_TETHERING, 0, MARK_UNSET, MARK_UNSET,
+ inputInterface, OIF_NONE, INVALID_UID, INVALID_UID);
+ }
+
+ if (ret == -ENOENT) {
+ return 0;
+ } else {
+ return ret;
+ }
+}
+
} // namespace
int RouteController::Init(unsigned localNetId) {
if (int ret = addUnreachableRule()) {
return ret;
}
+ // Don't complain if we can't add the dummy network, since not all devices support it.
+ configureDummyNetwork();
+
updateTableNamesFile();
return 0;
}
if (int ret = flushRoutes(interface)) {
return ret;
}
+ if (int ret = clearTetheringRules(interface)) {
+ return ret;
+ }
updateTableNamesFile();
return 0;
}
return modifyPhysicalNetwork(netId, interface, oldPermission, ACTION_DEL);
}
+int RouteController::addUsersToRejectNonSecureNetworkRule(const UidRanges& uidRanges) {
+ return modifyRejectNonSecureNetworkRule(uidRanges, true);
+}
+
+int RouteController::removeUsersFromRejectNonSecureNetworkRule(const UidRanges& uidRanges) {
+ return modifyRejectNonSecureNetworkRule(uidRanges, false);
+}
+
int RouteController::addUsersToVirtualNetwork(unsigned netId, const char* interface, bool secure,
const UidRanges& uidRanges) {
return modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_ADD,