Move the 464xlat control plane from clatd to netd.
Some of this code comes from clatd and will be deleted from there
once this topic is merged.
Bug: 65674744
Test: builds, boots
Test: system/netd/tests/runtests.sh
Change-Id: Ifa30652a853401a401d0b74d259d4d27d322e758
diff --git a/server/ClatdController.cpp b/server/ClatdController.cpp
index 0290303..142c4ca 100644
--- a/server/ClatdController.cpp
+++ b/server/ClatdController.cpp
@@ -19,7 +19,9 @@
#include <map>
#include <string>
+#include <arpa/inet.h>
#include <errno.h>
+#include <netinet/in.h>
#include <spawn.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -28,6 +30,12 @@
#define LOG_TAG "ClatdController"
#include <log/log.h>
+#include "android-base/unique_fd.h"
+
+extern "C" {
+#include "netutils/checksum.h"
+}
+
#include "Fwmark.h"
#include "NetdConstants.h"
#include "NetworkController.h"
@@ -35,6 +43,14 @@
static const char* kClatdPath = "/system/bin/clatd";
+// For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses in
+// 192.0.0.0/29 (RFC 7335).
+static const char* kV4AddrString = "192.0.0.4";
+static const in_addr kV4Addr = {inet_addr(kV4AddrString)};
+static const int kV4AddrLen = 29;
+
+using android::base::unique_fd;
+
namespace android {
namespace net {
@@ -45,89 +61,231 @@
ClatdController::~ClatdController() {
}
-// Returns the PID of the clatd running on interface |interface|, or 0 if clatd is not running on
-// |interface|.
-pid_t ClatdController::getClatdPid(const char* interface) {
- auto it = mClatdPids.find(interface);
- return (it == mClatdPids.end() ? 0 : it->second);
+bool ClatdController::isIpv4AddressFree(in_addr_t addr) {
+ int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (s == -1) {
+ return 0;
+ }
+
+ // Attempt to connect to the address. If the connection succeeds and getsockname returns the
+ // same then the address is already assigned to the system and we can't use it.
+ struct sockaddr_in sin = {.sin_family = AF_INET, .sin_addr = {addr}, .sin_port = 53};
+ socklen_t len = sizeof(sin);
+ bool inuse = connect(s, (struct sockaddr*)&sin, sizeof(sin)) == 0 &&
+ getsockname(s, (struct sockaddr*)&sin, &len) == 0 && (size_t)len >= sizeof(sin) &&
+ sin.sin_addr.s_addr == addr;
+
+ close(s);
+ return !inuse;
}
-int ClatdController::startClatd(const char* interface) {
- pid_t pid = getClatdPid(interface);
+// Picks a free IPv4 address, starting from ip and trying all addresses in the prefix in order.
+// ip - the IP address from the configuration file
+// prefixlen - the length of the prefix from which addresses may be selected.
+// returns: the IPv4 address, or INADDR_NONE if no addresses were available
+in_addr_t ClatdController::selectIpv4Address(const in_addr ip, int16_t prefixlen) {
+ // Don't accept prefixes that are too large because we scan addresses one by one.
+ if (prefixlen < 16 || prefixlen > 32) {
+ return INADDR_NONE;
+ }
- if (pid != 0) {
- ALOGE("clatd pid=%d already started on %s", pid, interface);
- errno = EBUSY;
+ // All these are in host byte order.
+ in_addr_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen);
+ in_addr_t ipv4 = ntohl(ip.s_addr);
+ in_addr_t first_ipv4 = ipv4;
+ in_addr_t prefix = ipv4 & mask;
+
+ // Pick the first IPv4 address in the pool, wrapping around if necessary.
+ // So, for example, 192.0.0.4 -> 192.0.0.5 -> 192.0.0.6 -> 192.0.0.7 -> 192.0.0.0.
+ do {
+ if (isIpv4AddressFreeFunc(htonl(ipv4))) {
+ return htonl(ipv4);
+ }
+ ipv4 = prefix | ((ipv4 + 1) & ~mask);
+ } while (ipv4 != first_ipv4);
+
+ return INADDR_NONE;
+}
+
+// Alters the bits in the IPv6 address to make them checksum neutral with v4 and nat64Prefix.
+void ClatdController::makeChecksumNeutral(in6_addr* v6, const in_addr v4,
+ const in6_addr& nat64Prefix) {
+ // Fill last 8 bytes of IPv6 address with random bits.
+ arc4random_buf(&v6->s6_addr[8], 8);
+
+ // Make the IID checksum-neutral. That is, make it so that:
+ // checksum(Local IPv4 | Remote IPv4) = checksum(Local IPv6 | Remote IPv6)
+ // in other words (because remote IPv6 = NAT64 prefix | Remote IPv4):
+ // checksum(Local IPv4) = checksum(Local IPv6 | NAT64 prefix)
+ // Do this by adjusting the two bytes in the middle of the IID.
+
+ uint16_t middlebytes = (v6->s6_addr[11] << 8) + v6->s6_addr[12];
+
+ uint32_t c1 = ip_checksum_add(0, &v4, sizeof(v4));
+ uint32_t c2 = ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
+ ip_checksum_add(0, v6, sizeof(*v6));
+
+ uint16_t delta = ip_checksum_adjust(middlebytes, c1, c2);
+ v6->s6_addr[11] = delta >> 8;
+ v6->s6_addr[12] = delta & 0xff;
+}
+
+// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
+int ClatdController::generateIpv6Address(const char* iface, const in_addr v4,
+ const in6_addr& nat64Prefix, in6_addr* v6) {
+ unique_fd s(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+ if (s == -1) return -errno;
+
+ if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) == -1) {
return -errno;
}
- // Pass in the interface, a netid to use for DNS lookups, and a fwmark for outgoing packets.
- unsigned netId = mNetCtrl->getNetworkForInterface(interface);
+ sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = nat64Prefix};
+ if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)) == -1) {
+ return -errno;
+ }
+
+ socklen_t len = sizeof(sin6);
+ if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len) == -1) {
+ return -errno;
+ }
+
+ *v6 = sin6.sin6_addr;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(v6) || IN6_IS_ADDR_LOOPBACK(v6) || IN6_IS_ADDR_LINKLOCAL(v6) ||
+ IN6_IS_ADDR_SITELOCAL(v6) || IN6_IS_ADDR_ULA(v6)) {
+ return -ENETUNREACH;
+ }
+
+ makeChecksumNeutral(v6, v4, nat64Prefix);
+
+ return 0;
+}
+
+// Finds the tracker of the clatd running on interface |interface|, or nullptr if clatd has not been
+// started on |interface|.
+ClatdController::ClatdTracker* ClatdController::getClatdTracker(const std::string& interface) {
+ auto it = mClatdTrackers.find(interface);
+ return (it == mClatdTrackers.end() ? nullptr : &it->second);
+}
+
+// Initializes a ClatdTracker for the specified interface.
+int ClatdController::ClatdTracker::init(const std::string& interface,
+ const std::string& nat64Prefix) {
+ netId = netCtrl->getNetworkForInterface(interface.c_str());
if (netId == NETID_UNSET) {
- ALOGE("interface %s not assigned to any netId", interface);
+ ALOGE("Interface %s not assigned to any netId", interface.c_str());
errno = ENODEV;
return -errno;
}
- char netIdString[UINT32_STRLEN];
- snprintf(netIdString, sizeof(netIdString), "%u", netId);
-
- Fwmark fwmark;
fwmark.netId = netId;
fwmark.explicitlySelected = true;
fwmark.protectedFromVpn = true;
fwmark.permission = PERMISSION_SYSTEM;
- char fwmarkString[UINT32_HEX_STRLEN];
snprintf(fwmarkString, sizeof(fwmarkString), "0x%x", fwmark.intValue);
+ snprintf(netIdString, sizeof(netIdString), "%u", netId);
+ strlcpy(iface, interface.c_str(), sizeof(iface));
- char* interfaceName = const_cast<char*>(interface);
+ // Pass in everything that clatd needs: interface, a netid to use for DNS lookups, a fwmark for
+ // outgoing packets, the NAT64 prefix, and the IPv4 and IPv6 addresses.
+ // Validate the prefix and strip off the prefix length.
+ uint8_t family;
+ uint8_t prefixLen;
+ int res = parsePrefix(nat64Prefix.c_str(), &family, &dst, sizeof(dst), &prefixLen);
+ // clatd only supports /96 prefixes.
+ if (res != sizeof(dst)) return res;
+ if (family != AF_INET6) return -EAFNOSUPPORT;
+ if (prefixLen != 96) return -EINVAL;
+ if (!inet_ntop(AF_INET6, &dst, dstString, sizeof(dstString))) return -errno;
- ALOGD("starting clatd on %s", interface);
+ // Pick an IPv4 address.
+ // TODO: this picks the address based on other addresses that are assigned to interfaces, but
+ // the address is only actually assigned to an interface once clatd starts up. So we could end
+ // up with two clatd instances with the same IPv4 address.
+ // Stop doing this and instead pick a free one from the kV4Addr pool.
+ in_addr v4 = {selectIpv4Address(kV4Addr, kV4AddrLen)};
+ if (v4.s_addr == INADDR_NONE) {
+ ALOGE("No free IPv4 address in %s/%d", kV4AddrString, kV4AddrLen);
+ return -EADDRNOTAVAIL;
+ }
+ if (!inet_ntop(AF_INET, &v4, v4Str, sizeof(v4Str))) return -errno;
+
+ // Generate a checksum-neutral IID.
+ if (generateIpv6Address(iface, v4, dst, &v6)) {
+ ALOGE("Unable to find global source address on %s for %s", iface, dstString);
+ return -EADDRNOTAVAIL;
+ }
+ if (!inet_ntop(AF_INET6, &v6, v6Str, sizeof(v6Str))) return -errno;
+
+ ALOGD("starting clatd on %s v4=%s v6=%s dst=%s", iface, v4Str, v6Str, dstString);
+ return 0;
+}
+
+int ClatdController::startClatd(const std::string& interface, const std::string& nat64Prefix,
+ std::string* v6Str) {
+ ClatdTracker* existing = getClatdTracker(interface);
+ if (existing != nullptr) {
+ ALOGE("clatd pid=%d already started on %s", existing->pid, interface.c_str());
+ errno = EBUSY;
+ return -errno;
+ }
+
+ ClatdTracker tracker(mNetCtrl);
+ if (int ret = tracker.init(interface, nat64Prefix)) {
+ return ret;
+ }
std::string progname("clatd-");
- progname += interface;
+ progname += tracker.iface;
- char* args[] = {(char*)progname.c_str(),
- (char*)"-i",
- interfaceName,
- (char*)"-n",
- netIdString,
- (char*)"-m",
- fwmarkString,
+ // clang-format off
+ char* args[] = {(char*) progname.c_str(),
+ (char*) "-i", tracker.iface,
+ (char*) "-n", tracker.netIdString,
+ (char*) "-m", tracker.fwmarkString,
+ (char*) "-p", tracker.dstString,
+ (char*) "-4", tracker.v4Str,
+ (char*) "-6", tracker.v6Str,
nullptr};
+ // clang-format on
+
// Specify no flags and no actions, posix_spawn will use vfork and is
// guaranteed to return only once exec has been called.
- int res = posix_spawn(&pid, kClatdPath, nullptr, nullptr, args, nullptr);
+ int res = posix_spawn(&tracker.pid, kClatdPath, nullptr, nullptr, args, nullptr);
if (res) {
ALOGE("posix_spawn failed (%s)", strerror(res));
return -res;
}
- mClatdPids[interface] = pid;
- ALOGD("clatd started on %s", interface);
+ mClatdTrackers[interface] = tracker;
+ ALOGD("clatd started on %s", interface.c_str());
+ *v6Str = tracker.v6Str;
return 0;
}
-int ClatdController::stopClatd(const char* interface) {
- pid_t pid = getClatdPid(interface);
+int ClatdController::stopClatd(const std::string& interface) {
+ ClatdTracker* tracker = getClatdTracker(interface);
- if (pid == 0) {
+ if (tracker == nullptr) {
ALOGE("clatd already stopped");
- return -EREMOTEIO;
+ return -ENODEV;
}
- ALOGD("Stopping clatd pid=%d on %s", pid, interface);
+ ALOGD("Stopping clatd pid=%d on %s", tracker->pid, interface.c_str());
- kill(pid, SIGTERM);
- waitpid(pid, nullptr, 0);
- mClatdPids.erase(interface);
+ kill(tracker->pid, SIGTERM);
+ waitpid(tracker->pid, nullptr, 0);
+ mClatdTrackers.erase(interface);
- ALOGD("clatd on %s stopped", interface);
+ ALOGD("clatd on %s stopped", interface.c_str());
return 0;
}
+auto ClatdController::isIpv4AddressFreeFunc = isIpv4AddressFree;
+
} // namespace net
} // namespace android