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:
commit
135a9e4e58
9 changed files with 1148 additions and 0 deletions
|
|
@ -32,6 +32,9 @@
|
|||
// TODO: move to presubmit when known green.
|
||||
{
|
||||
"name": "bpf_existence_test"
|
||||
},
|
||||
{
|
||||
"name": "libclat_test"
|
||||
}
|
||||
],
|
||||
"mainline-presubmit": [
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
269
service/jni/com_android_server_connectivity_ClatCoordinator.cpp
Normal file
269
service/jni/com_android_server_connectivity_ClatCoordinator.cpp
Normal 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
49
service/native/libs/libclat/Android.bp
Normal file
49
service/native/libs/libclat/Android.bp
Normal 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,
|
||||
}
|
||||
268
service/native/libs/libclat/clatutils.cpp
Normal file
268
service/native/libs/libclat/clatutils.cpp
Normal 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
|
||||
187
service/native/libs/libclat/clatutils_test.cpp
Normal file
187
service/native/libs/libclat/clatutils_test.cpp
Normal 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
|
||||
37
service/native/libs/libclat/include/libclat/clatutils.h
Normal file
37
service/native/libs/libclat/include/libclat/clatutils.h
Normal 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
|
||||
324
service/src/com/android/server/connectivity/ClatCoordinator.java
Normal file
324
service/src/com/android/server/connectivity/ClatCoordinator.java
Normal 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;
|
||||
}
|
||||
Loading…
Reference in a new issue