Merge changes I6745a615,Ic50dc122,I38729c23,I6d1da4f1,I1cef6e09, ... am: e0322e3fd1

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/1956037

Change-Id: Ic7a302654e221eb242adf4aa3258e05ae866449c
This commit is contained in:
Treehugger Robot 2022-01-21 03:02:47 +00:00 • committed by Automerger Merge Worker
commit 135a9e4e58
9 changed files with 1148 additions and 0 deletions

View file

@ -32,6 +32,9 @@
// TODO: move to presubmit when known green.
{
"name": "bpf_existence_test"
},
{
"name": "libclat_test"
}
],
"mainline-presubmit": [

View file

@ -57,12 +57,18 @@ cc_library_shared {
],
srcs: [
"jni/com_android_server_TestNetworkService.cpp",
"jni/com_android_server_connectivity_ClatCoordinator.cpp",
"jni/onload.cpp",
],
stl: "libc++_static",
header_libs: [
"libbase_headers",
],
static_libs: [
"libclat",
"libip_checksum",
"libnetjniutils",
],
shared_libs: [
"liblog",
"libnativehelper",

View file

@ -0,0 +1,269 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/if_tun.h>
#include <linux/ioctl.h>
#include <nativehelper/JNIHelp.h>
#include <net/if.h>
#include <netjniutils/netjniutils.h>
#include "libclat/clatutils.h"
#include "nativehelper/scoped_utf_chars.h"
// Sync from system/netd/include/netid_client.h
#define MARK_UNSET 0u
namespace android {
static void throwIOException(JNIEnv* env, const char* msg, int error) {
jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
}
jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv* env,
jobject clazz,
jstring v4addr,
jint prefixlen) {
ScopedUtfChars address(env, v4addr);
in_addr ip;
if (inet_pton(AF_INET, address.c_str(), &ip) != 1) {
throwIOException(env, "invalid address", EINVAL);
return nullptr;
}
// 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 = {net::clat::selectIpv4Address(ip, prefixlen)};
if (v4.s_addr == INADDR_NONE) {
jniThrowExceptionFmt(env, "java/io/IOException", "No free IPv4 address in %s/%d",
address.c_str(), prefixlen);
return nullptr;
}
char addrstr[INET_ADDRSTRLEN];
if (!inet_ntop(AF_INET, (void*)&v4, addrstr, sizeof(addrstr))) {
throwIOException(env, "invalid address", EADDRNOTAVAIL);
return nullptr;
}
return env->NewStringUTF(addrstr);
}
// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
jstring com_android_server_connectivity_ClatCoordinator_generateIpv6Address(
JNIEnv* env, jobject clazz, jstring ifaceStr, jstring v4Str, jstring prefix64Str) {
ScopedUtfChars iface(env, ifaceStr);
ScopedUtfChars addr4(env, v4Str);
ScopedUtfChars prefix64(env, prefix64Str);
if (iface.c_str() == nullptr) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid null interface name");
return nullptr;
}
in_addr v4;
if (inet_pton(AF_INET, addr4.c_str(), &v4) != 1) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid clat v4 address %s",
addr4.c_str());
return nullptr;
}
in6_addr nat64Prefix;
if (inet_pton(AF_INET6, prefix64.c_str(), &nat64Prefix) != 1) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid prefix %s", prefix64.c_str());
return nullptr;
}
in6_addr v6;
if (net::clat::generateIpv6Address(iface.c_str(), v4, nat64Prefix, &v6)) {
jniThrowExceptionFmt(env, "java/io/IOException",
"Unable to find global source address on %s for %s", iface.c_str(),
prefix64.c_str());
return nullptr;
}
char addrstr[INET6_ADDRSTRLEN];
if (!inet_ntop(AF_INET6, (void*)&v6, addrstr, sizeof(addrstr))) {
throwIOException(env, "invalid address", EADDRNOTAVAIL);
return nullptr;
}
return env->NewStringUTF(addrstr);
}
static jint com_android_server_connectivity_ClatCoordinator_createTunInterface(JNIEnv* env,
jobject clazz,
jstring tuniface) {
ScopedUtfChars v4interface(env, tuniface);
// open the tun device in non blocking mode as required by clatd
jint fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC);
if (fd == -1) {
jniThrowExceptionFmt(env, "java/io/IOException", "open tun device failed (%s)",
strerror(errno));
return -1;
}
struct ifreq ifr = {
.ifr_flags = IFF_TUN,
};
strlcpy(ifr.ifr_name, v4interface.c_str(), sizeof(ifr.ifr_name));
if (ioctl(fd, TUNSETIFF, &ifr, sizeof(ifr))) {
close(fd);
jniThrowExceptionFmt(env, "java/io/IOException", "ioctl(TUNSETIFF) failed (%s)",
strerror(errno));
return -1;
}
return fd;
}
static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* env, jobject clazz,
jstring platSubnet,
jint plat_suffix, jint mark) {
ScopedUtfChars platSubnetStr(env, platSubnet);
in6_addr plat_subnet;
if (inet_pton(AF_INET6, platSubnetStr.c_str(), &plat_subnet) != 1) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid plat prefix address %s",
platSubnetStr.c_str());
return -1;
}
int ret = net::clat::detect_mtu(&plat_subnet, plat_suffix, mark);
if (ret < 0) {
jniThrowExceptionFmt(env, "java/io/IOException", "detect mtu failed: %s", strerror(-ret));
return -1;
}
return ret;
}
static jint com_android_server_connectivity_ClatCoordinator_openPacketSocket(JNIEnv* env,
jobject clazz) {
// Will eventually be bound to htons(ETH_P_IPV6) protocol,
// but only after appropriate bpf filter is attached.
int sock = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (sock < 0) {
throwIOException(env, "packet socket failed", errno);
return -1;
}
return sock;
}
static jint com_android_server_connectivity_ClatCoordinator_openRawSocket6(JNIEnv* env,
jobject clazz,
jint mark) {
int sock = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_RAW);
if (sock < 0) {
throwIOException(env, "raw socket failed", errno);
return -1;
}
// TODO: check the mark validation
if (mark != MARK_UNSET && setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
throwIOException(env, "could not set mark on raw socket", errno);
close(sock);
return -1;
}
return sock;
}
static void com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt(
JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
if (sock < 0) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
return;
}
ScopedUtfChars addrStr(env, addr6);
in6_addr addr;
if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s",
addrStr.c_str());
return;
}
struct ipv6_mreq mreq = {addr, ifindex};
int ret = setsockopt(sock, SOL_IPV6, IPV6_JOIN_ANYCAST, &mreq, sizeof(mreq));
if (ret) {
jniThrowExceptionFmt(env, "java/io/IOException", "setsockopt IPV6_JOIN_ANYCAST failed: %s",
strerror(errno));
return;
}
}
static void com_android_server_connectivity_ClatCoordinator_configurePacketSocket(
JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
ScopedUtfChars addrStr(env, addr6);
int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
if (sock < 0) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
return;
}
in6_addr addr;
if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s",
addrStr.c_str());
return;
}
int ret = net::clat::configure_packet_socket(sock, &addr, ifindex);
if (ret < 0) {
throwIOException(env, "configure packet socket failed", -ret);
return;
}
}
/*
* JNI registration.
*/
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{"native_selectIpv4Address", "(Ljava/lang/String;I)Ljava/lang/String;",
(void*)com_android_server_connectivity_ClatCoordinator_selectIpv4Address},
{"native_generateIpv6Address",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
(void*)com_android_server_connectivity_ClatCoordinator_generateIpv6Address},
{"native_createTunInterface", "(Ljava/lang/String;)I",
(void*)com_android_server_connectivity_ClatCoordinator_createTunInterface},
{"native_detectMtu", "(Ljava/lang/String;II)I",
(void*)com_android_server_connectivity_ClatCoordinator_detectMtu},
{"native_openPacketSocket", "()I",
(void*)com_android_server_connectivity_ClatCoordinator_openPacketSocket},
{"native_openRawSocket6", "(I)I",
(void*)com_android_server_connectivity_ClatCoordinator_openRawSocket6},
{"native_addAnycastSetsockopt", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
(void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt},
{"native_configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
(void*)com_android_server_connectivity_ClatCoordinator_configurePacketSocket},
};
int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
return jniRegisterNativeMethods(env, "com/android/server/connectivity/ClatCoordinator",
gMethods, NELEM(gMethods));
}
}; // namespace android

View file

@ -20,6 +20,7 @@
namespace android {
int register_android_server_TestNetworkService(JNIEnv* env);
int register_android_server_connectivity_ClatCoordinator(JNIEnv* env);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@ -32,6 +33,10 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
return JNI_ERR;
}
if (register_android_server_connectivity_ClatCoordinator(env) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}

View file

@ -0,0 +1,49 @@
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_library_static {
name: "libclat",
defaults: ["netd_defaults"],
srcs: ["clatutils.cpp"],
stl: "libc++_static",
static_libs: ["libip_checksum"],
shared_libs: ["liblog"],
export_include_dirs: ["include"],
min_sdk_version: "30",
apex_available: ["com.android.tethering"],
}
cc_test {
name: "libclat_test",
defaults: ["netd_defaults"],
test_suites: ["device-tests"],
srcs: [
"clatutils_test.cpp",
],
static_libs: [
"libbase",
"libclat",
"libip_checksum",
"libnetd_test_tun_interface",
],
shared_libs: [
"liblog",
"libnetutils",
],
require_root: true,
}

View file

@ -0,0 +1,268 @@
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#define LOG_TAG "clatutils"
#include "libclat/clatutils.h"
#include <errno.h>
#include <linux/filter.h>
#include <linux/if_packet.h>
#include <linux/if_tun.h>
#include <log/log.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern "C" {
#include "checksum.h"
}
// Sync from external/android-clat/clatd.h
#define MAXMTU 65536
#define PACKETLEN (MAXMTU + sizeof(struct tun_pi))
// Sync from system/netd/include/netid_client.h.
#define MARK_UNSET 0u
namespace android {
namespace net {
namespace clat {
bool 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_port = htons(53),
.sin_addr = {addr},
};
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;
}
// 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 selectIpv4Address(const in_addr ip, int16_t prefixlen) {
return selectIpv4AddressInternal(ip, prefixlen, isIpv4AddressFree);
}
// Only allow testing to use this function directly. Otherwise call selectIpv4Address(ip, pfxlen)
// which has applied valid isIpv4AddressFree function pointer.
in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen,
isIpv4AddrFreeFn isIpv4AddressFreeFunc) {
// Impossible! Only test allows to apply fn.
if (isIpv4AddressFreeFunc == nullptr) {
return INADDR_NONE;
}
// Don't accept prefixes that are too large because we scan addresses one by one.
if (prefixlen < 16 || prefixlen > 32) {
return INADDR_NONE;
}
// 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 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 generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
in6_addr* v6) {
int 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) {
close(s);
return -errno;
}
sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = nat64Prefix};
if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)) == -1) {
close(s);
return -errno;
}
socklen_t len = sizeof(sin6);
if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len) == -1) {
close(s);
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)) {
close(s);
return -ENETUNREACH;
}
makeChecksumNeutral(v6, v4, nat64Prefix);
close(s);
return 0;
}
int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark) {
// Create an IPv6 UDP socket.
int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (s < 0) {
int ret = errno;
ALOGE("socket(AF_INET6, SOCK_DGRAM, 0) failed: %s", strerror(errno));
return -ret;
}
// Socket's mark affects routing decisions (network selection)
if ((mark != MARK_UNSET) && setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
int ret = errno;
ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(errno));
close(s);
return -ret;
}
// Try to connect udp socket to plat_subnet(96 bits):plat_suffix(32 bits)
struct sockaddr_in6 dst = {
.sin6_family = AF_INET6,
.sin6_addr = *plat_subnet,
};
dst.sin6_addr.s6_addr32[3] = plat_suffix;
if (connect(s, (struct sockaddr*)&dst, sizeof(dst))) {
int ret = errno;
ALOGE("connect() failed: %s", strerror(errno));
close(s);
return -ret;
}
// Fetch the socket's IPv6 mtu - this is effectively fetching mtu from routing table
int mtu;
socklen_t sz_mtu = sizeof(mtu);
if (getsockopt(s, SOL_IPV6, IPV6_MTU, &mtu, &sz_mtu)) {
int ret = errno;
ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) failed: %s", strerror(errno));
close(s);
return -ret;
}
if (sz_mtu != sizeof(mtu)) {
ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) returned unexpected size: %d", sz_mtu);
close(s);
return -EFAULT;
}
close(s);
return mtu;
}
/* function: configure_packet_socket
* Binds the packet socket and attaches the receive filter to it.
* sock - the socket to configure
* addr - the IP address to filter
* ifindex - index of interface to add the filter to
* returns: 0 on success, -errno on failure
*/
int configure_packet_socket(int sock, in6_addr* addr, int ifindex) {
uint32_t* ipv6 = addr->s6_addr32;
// clang-format off
struct sock_filter filter_code[] = {
// Load the first four bytes of the IPv6 destination address (starts 24 bytes in).
// Compare it against the first four bytes of our IPv6 address, in host byte order (BPF loads
// are always in host byte order). If it matches, continue with next instruction (JMP 0). If it
// doesn't match, jump ahead to statement that returns 0 (ignore packet). Repeat for the other
// three words of the IPv6 address, and if they all match, return PACKETLEN (accept packet).
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 24),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[0]), 0, 7),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 28),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[1]), 0, 5),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 32),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[2]), 0, 3),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 36),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[3]), 0, 1),
BPF_STMT(BPF_RET | BPF_K, PACKETLEN),
BPF_STMT(BPF_RET | BPF_K, 0),
};
// clang-format on
struct sock_fprog filter = {sizeof(filter_code) / sizeof(filter_code[0]), filter_code};
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) {
int res = errno;
ALOGE("attach packet filter failed: %s", strerror(errno));
return -res;
}
struct sockaddr_ll sll = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IPV6),
.sll_ifindex = ifindex,
.sll_pkttype =
PACKET_OTHERHOST, // The 464xlat IPv6 address is not assigned to the kernel.
};
if (bind(sock, (struct sockaddr*)&sll, sizeof(sll))) {
int res = errno;
ALOGE("binding packet socket: %s", strerror(errno));
return -res;
}
return 0;
}
} // namespace clat
} // namespace net
} // namespace android

View file

@ -0,0 +1,187 @@
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "libclat/clatutils.h"
#include <android-base/stringprintf.h>
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <linux/if_packet.h>
#include <linux/if_tun.h>
#include "tun_interface.h"
extern "C" {
#include "checksum.h"
}
// Default translation parameters.
static const char kIPv4LocalAddr[] = "192.0.0.4";
namespace android {
namespace net {
namespace clat {
using android::net::TunInterface;
using base::StringPrintf;
class ClatUtils : public ::testing::Test {};
// Mock functions for isIpv4AddressFree.
bool neverFree(in_addr_t /* addr */) {
return 0;
}
bool alwaysFree(in_addr_t /* addr */) {
return 1;
}
bool only2Free(in_addr_t addr) {
return (ntohl(addr) & 0xff) == 2;
}
bool over6Free(in_addr_t addr) {
return (ntohl(addr) & 0xff) >= 6;
}
bool only10Free(in_addr_t addr) {
return (ntohl(addr) & 0xff) == 10;
}
// Apply mocked isIpv4AddressFree function for selectIpv4Address test.
in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen,
isIpv4AddrFreeFn fn /* mocked function */) {
// Call internal function to replace isIpv4AddressFreeFn for testing.
return selectIpv4AddressInternal(ip, prefixlen, fn);
}
TEST_F(ClatUtils, SelectIpv4Address) {
struct in_addr addr;
inet_pton(AF_INET, kIPv4LocalAddr, &addr);
// If no addresses are free, return INADDR_NONE.
EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, neverFree));
EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 16, neverFree));
// If the configured address is free, pick that. But a prefix that's too big is invalid.
EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 29, alwaysFree));
EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 20, alwaysFree));
EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 15, alwaysFree));
// A prefix length of 32 works, but anything above it is invalid.
EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 32, alwaysFree));
EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 33, alwaysFree));
// If another address is free, pick it.
EXPECT_EQ(inet_addr("192.0.0.6"), selectIpv4Address(addr, 29, over6Free));
// Check that we wrap around to addresses that are lower than the first address.
EXPECT_EQ(inet_addr("192.0.0.2"), selectIpv4Address(addr, 29, only2Free));
EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 30, only2Free));
// If a free address exists outside the prefix, we don't pick it.
EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, only10Free));
EXPECT_EQ(inet_addr("192.0.0.10"), selectIpv4Address(addr, 24, only10Free));
// Now try using the real function which sees if IP addresses are free using bind().
// Assume that the machine running the test has the address 127.0.0.1, but not 8.8.8.8.
addr.s_addr = inet_addr("8.8.8.8");
EXPECT_EQ(inet_addr("8.8.8.8"), selectIpv4Address(addr, 29));
addr.s_addr = inet_addr("127.0.0.1");
EXPECT_EQ(inet_addr("127.0.0.2"), selectIpv4Address(addr, 29));
}
TEST_F(ClatUtils, MakeChecksumNeutral) {
// We can't test generateIPv6Address here since it requires manipulating routing, which we can't
// do without talking to the real netd on the system.
uint32_t rand = arc4random_uniform(0xffffffff);
uint16_t rand1 = rand & 0xffff;
uint16_t rand2 = (rand >> 16) & 0xffff;
std::string v6PrefixStr = StringPrintf("2001:db8:%x:%x", rand1, rand2);
std::string v6InterfaceAddrStr = StringPrintf("%s::%x:%x", v6PrefixStr.c_str(), rand2, rand1);
std::string nat64PrefixStr = StringPrintf("2001:db8:%x:%x::", rand2, rand1);
in_addr v4 = {inet_addr(kIPv4LocalAddr)};
in6_addr v6InterfaceAddr;
ASSERT_TRUE(inet_pton(AF_INET6, v6InterfaceAddrStr.c_str(), &v6InterfaceAddr));
in6_addr nat64Prefix;
ASSERT_TRUE(inet_pton(AF_INET6, nat64PrefixStr.c_str(), &nat64Prefix));
// Generate a boatload of random IIDs.
int onebits = 0;
uint64_t prev_iid = 0;
for (int i = 0; i < 100000; i++) {
in6_addr v6 = v6InterfaceAddr;
makeChecksumNeutral(&v6, v4, nat64Prefix);
// Check the generated IP address is in the same prefix as the interface IPv6 address.
EXPECT_EQ(0, memcmp(&v6, &v6InterfaceAddr, 8));
// Check that consecutive IIDs are not the same.
uint64_t iid = *(uint64_t*)(&v6.s6_addr[8]);
ASSERT_TRUE(iid != prev_iid)
<< "Two consecutive random IIDs are the same: " << std::showbase << std::hex << iid
<< "\n";
prev_iid = iid;
// Check that the IID is checksum-neutral with the NAT64 prefix and the
// local prefix.
uint16_t c1 = ip_checksum_finish(ip_checksum_add(0, &v4, sizeof(v4)));
uint16_t c2 = ip_checksum_finish(ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
ip_checksum_add(0, &v6, sizeof(v6)));
if (c1 != c2) {
char v6Str[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &v6, v6Str, sizeof(v6Str));
FAIL() << "Bad IID: " << v6Str << " not checksum-neutral with " << kIPv4LocalAddr
<< " and " << nat64PrefixStr.c_str() << std::showbase << std::hex
<< "\n IPv4 checksum: " << c1 << "\n IPv6 checksum: " << c2 << "\n";
}
// Check that IIDs are roughly random and use all the bits by counting the
// total number of bits set to 1 in a random sample of 100000 generated IIDs.
onebits += __builtin_popcountll(*(uint64_t*)&iid);
}
EXPECT_LE(3190000, onebits);
EXPECT_GE(3210000, onebits);
}
TEST_F(ClatUtils, DetectMtu) {
// ::1 with bottom 32 bits set to 1 is still ::1 which routes via lo with mtu of 64KiB
ASSERT_EQ(detect_mtu(&in6addr_loopback, htonl(1), 0 /*MARK_UNSET*/), 65536);
}
TEST_F(ClatUtils, ConfigurePacketSocket) {
// Create an interface for configure_packet_socket to attach socket filter to.
TunInterface v6Iface;
ASSERT_EQ(0, v6Iface.init());
int s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
EXPECT_LE(0, s);
struct in6_addr addr6;
EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::f00", &addr6));
EXPECT_EQ(0, configure_packet_socket(s, &addr6, v6Iface.ifindex()));
// Check that the packet socket is bound to the interface. We can't check the socket filter
// because there is no way to fetch it from the kernel.
sockaddr_ll sll;
socklen_t len = sizeof(sll);
ASSERT_EQ(0, getsockname(s, reinterpret_cast<sockaddr*>(&sll), &len));
EXPECT_EQ(htons(ETH_P_IPV6), sll.sll_protocol);
EXPECT_EQ(sll.sll_ifindex, v6Iface.ifindex());
close(s);
v6Iface.destroy();
}
} // namespace clat
} // namespace net
} // namespace android

View file

@ -0,0 +1,37 @@
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <netinet/in.h>
#include <netinet/in6.h>
namespace android {
namespace net {
namespace clat {
bool isIpv4AddressFree(in_addr_t addr);
in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen);
void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix);
int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
in6_addr* v6);
int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark);
int configure_packet_socket(int sock, in6_addr* addr, int ifindex);
// For testing
typedef bool (*isIpv4AddrFreeFn)(in_addr_t);
in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen, isIpv4AddrFreeFn fn);
} // namespace clat
} // namespace net
} // namespace android

View file

@ -0,0 +1,324 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import static android.net.INetd.IF_STATE_UP;
import static android.net.INetd.PERMISSION_SYSTEM;
import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.INetd;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.InterfaceParams;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
/**
* This coordinator is responsible for providing clat relevant functionality.
*
* {@hide}
*/
public class ClatCoordinator {
private static final String TAG = ClatCoordinator.class.getSimpleName();
// Sync from external/android-clat/clatd.c
// 40 bytes IPv6 header - 20 bytes IPv4 header + 8 bytes fragment header.
@VisibleForTesting
static final int MTU_DELTA = 28;
@VisibleForTesting
static final int CLAT_MAX_MTU = 65536;
// This must match the interface prefix in clatd.c.
private static final String CLAT_PREFIX = "v4-";
// For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses
// in 192.0.0.0/29 (RFC 7335).
@VisibleForTesting
static final String INIT_V4ADDR_STRING = "192.0.0.4";
@VisibleForTesting
static final int INIT_V4ADDR_PREFIX_LEN = 29;
private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");
private static final int INVALID_IFINDEX = 0;
private static final int INVALID_PID = 0;
@NonNull
private final INetd mNetd;
@NonNull
private final Dependencies mDeps;
@Nullable
private String mIface = null;
private int mPid = INVALID_PID;
@VisibleForTesting
abstract static class Dependencies {
/**
* Get netd.
*/
@NonNull
public abstract INetd getNetd();
/**
* @see ParcelFileDescriptor#adoptFd(int).
*/
@NonNull
public ParcelFileDescriptor adoptFd(int fd) {
return ParcelFileDescriptor.adoptFd(fd);
}
/**
* Get interface index for a given interface.
*/
public int getInterfaceIndex(String ifName) {
final InterfaceParams params = InterfaceParams.getByName(ifName);
return params != null ? params.index : INVALID_IFINDEX;
}
/**
* Create tun interface for a given interface name.
*/
public int createTunInterface(@NonNull String tuniface) throws IOException {
return native_createTunInterface(tuniface);
}
/**
* Pick an IPv4 address for clat.
*/
@NonNull
public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
throws IOException {
return native_selectIpv4Address(v4addr, prefixlen);
}
/**
* Generate a checksum-neutral IID.
*/
@NonNull
public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
@NonNull String prefix64) throws IOException {
return native_generateIpv6Address(iface, v4, prefix64);
}
/**
* Detect MTU.
*/
public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
throws IOException {
return native_detectMtu(platSubnet, platSuffix, mark);
}
/**
* Open packet socket.
*/
public int openPacketSocket() throws IOException {
return native_openPacketSocket();
}
/**
* Open IPv6 raw socket and set SO_MARK.
*/
public int openRawSocket6(int mark) throws IOException {
return native_openRawSocket6(mark);
}
/**
* Add anycast setsockopt.
*/
public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex)
throws IOException {
native_addAnycastSetsockopt(sock, v6, ifindex);
}
/**
* Configure packet socket.
*/
public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex)
throws IOException {
native_configurePacketSocket(sock, v6, ifindex);
}
}
@VisibleForTesting
static int getFwmark(int netId) {
// See union Fwmark in system/netd/include/Fwmark.h
return (netId & 0xffff)
| 0x1 << 16 // protectedFromVpn: true
| 0x1 << 17 // explicitlySelected: true
| (PERMISSION_SYSTEM & 0x3) << 18;
}
@VisibleForTesting
static int adjustMtu(int mtu) {
// clamp to minimum ipv6 mtu - this probably cannot ever trigger
if (mtu < IPV6_MIN_MTU) mtu = IPV6_MIN_MTU;
// clamp to buffer size
if (mtu > CLAT_MAX_MTU) mtu = CLAT_MAX_MTU;
// decrease by ipv6(40) + ipv6 fragmentation header(8) vs ipv4(20) overhead of 28 bytes
mtu -= MTU_DELTA;
return mtu;
}
public ClatCoordinator(@NonNull Dependencies deps) {
mDeps = deps;
mNetd = mDeps.getNetd();
}
/**
* Start clatd for a given interface and NAT64 prefix.
*/
public String clatStart(final String iface, final int netId,
@NonNull final IpPrefix nat64Prefix)
throws IOException {
if (nat64Prefix.getPrefixLength() != 96) {
throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
}
// [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
final String v4;
try {
v4 = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN);
} catch (IOException e) {
throw new IOException("no IPv4 addresses were available for clat: " + e);
}
// [2] Generate a checksum-neutral IID.
final String pfx96 = nat64Prefix.getAddress().getHostAddress();
final String v6;
try {
v6 = mDeps.generateIpv6Address(iface, v4, pfx96);
} catch (IOException e) {
throw new IOException("no IPv6 addresses were available for clat: " + e);
}
// [3] Open, configure and bring up the tun interface.
// Create the v4-... tun interface.
final String tunIface = CLAT_PREFIX + iface;
final ParcelFileDescriptor tunFd;
try {
tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
} catch (IOException e) {
throw new IOException("Create tun interface " + tunIface + " failed: " + e);
}
// disable IPv6 on it - failing to do so is not a critical error
try {
mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
}
// Detect ipv4 mtu.
final Integer fwmark = getFwmark(netId);
final int detectedMtu = mDeps.detectMtu(pfx96,
ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
final int mtu = adjustMtu(detectedMtu);
Log.i(TAG, "ipv4 mtu is " + mtu);
// TODO: add setIptablesDropRule
// Config tun interface mtu, address and bring up.
try {
mNetd.interfaceSetMtu(tunIface, mtu);
} catch (RemoteException | ServiceSpecificException e) {
throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
}
final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
ifConfig.ifName = tunIface;
ifConfig.ipv4Addr = v4;
ifConfig.prefixLength = 32;
ifConfig.hwAddr = "";
ifConfig.flags = new String[] {IF_STATE_UP};
try {
mNetd.interfaceSetCfg(ifConfig);
} catch (RemoteException | ServiceSpecificException e) {
throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
+ ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
}
// [4] Open and configure local 464xlat read/write sockets.
// Opens a packet socket to receive IPv6 packets in clatd.
final ParcelFileDescriptor readSock6;
try {
// Use a JNI call to get native file descriptor instead of Os.socket() because we would
// like to use ParcelFileDescriptor to close file descriptor automatically. But ctor
// ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file
// descriptor to initialize ParcelFileDescriptor object instead.
readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
} catch (IOException e) {
throw new IOException("Open packet socket failed: " + e);
}
// Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
final ParcelFileDescriptor writeSock6;
try {
// Use a JNI call to get native file descriptor instead of Os.socket(). See above
// reason why we use jniOpenPacketSocket6().
writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
} catch (IOException e) {
throw new IOException("Open raw socket failed: " + e);
}
final int ifaceIndex = mDeps.getInterfaceIndex(iface);
if (ifaceIndex == INVALID_IFINDEX) {
throw new IOException("Fail to get interface index for interface " + iface);
}
// Start translating packets to the new prefix.
try {
mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex);
} catch (IOException e) {
throw new IOException("add anycast sockopt failed: " + e);
}
// Update our packet socket filter to reflect the new 464xlat IP address.
try {
mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex);
} catch (IOException e) {
throw new IOException("configure packet socket failed: " + e);
}
// TODO: start clatd and returns local xlat464 v6 address.
return null;
}
private static native String native_selectIpv4Address(String v4addr, int prefixlen)
throws IOException;
private static native String native_generateIpv6Address(String iface, String v4,
String prefix64) throws IOException;
private static native int native_createTunInterface(String tuniface) throws IOException;
private static native int native_detectMtu(String platSubnet, int platSuffix, int mark)
throws IOException;
private static native int native_openPacketSocket() throws IOException;
private static native int native_openRawSocket6(int mark) throws IOException;
private static native void native_addAnycastSetsockopt(FileDescriptor sock, String v6,
int ifindex) throws IOException;
private static native void native_configurePacketSocket(FileDescriptor sock, String v6,
int ifindex) throws IOException;
}