Merge changes from topic "preparation_fork_clatd_from_system_server"
* changes: [CLATJ#24] ClatCoordinatorTest: add basic unit tests [CLATJ#23] Close the file descriptor manually [CLATJ#22] ClatCoordinator: stop clatd [CLATJ#21] ClatCoordinator: start clatd
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#define LOG_TAG "jniClatCoordinator"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
@@ -22,6 +23,8 @@
|
||||
#include <log/log.h>
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
#include <net/if.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/wait.h>
|
||||
#include <string>
|
||||
|
||||
#include <netjniutils/netjniutils.h>
|
||||
@@ -33,9 +36,16 @@
|
||||
// Sync from system/netd/include/netid_client.h
|
||||
#define MARK_UNSET 0u
|
||||
|
||||
// Sync from system/netd/server/NetdConstants.h
|
||||
#define __INT_STRLEN(i) sizeof(#i)
|
||||
#define _INT_STRLEN(i) __INT_STRLEN(i)
|
||||
#define INT32_STRLEN _INT_STRLEN(INT32_MIN)
|
||||
|
||||
#define DEVICEPREFIX "v4-"
|
||||
|
||||
namespace android {
|
||||
static const char* kClatdPath = "/apex/com.android.tethering/bin/for-system/clatd";
|
||||
|
||||
static void throwIOException(JNIEnv* env, const char* msg, int error) {
|
||||
jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
|
||||
}
|
||||
@@ -282,8 +292,7 @@ int initTracker(const std::string& iface, const std::string& pfx96, const std::s
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: fork clatd and rename to .._startClatd.
|
||||
static jint com_android_server_connectivity_ClatCoordinator_maybeStartBpf(
|
||||
static jint com_android_server_connectivity_ClatCoordinator_startClatd(
|
||||
JNIEnv* env, jobject clazz, jobject tunJavaFd, jobject readSockJavaFd,
|
||||
jobject writeSockJavaFd, jstring iface, jstring pfx96, jstring v4, jstring v6) {
|
||||
ScopedUtfChars ifaceStr(env, iface);
|
||||
@@ -291,7 +300,121 @@ static jint com_android_server_connectivity_ClatCoordinator_maybeStartBpf(
|
||||
ScopedUtfChars v4Str(env, v4);
|
||||
ScopedUtfChars v6Str(env, v6);
|
||||
|
||||
// Start BPF if any
|
||||
int tunFd = netjniutils::GetNativeFileDescriptor(env, tunJavaFd);
|
||||
if (tunFd < 0) {
|
||||
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid tun file descriptor");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int readSock = netjniutils::GetNativeFileDescriptor(env, readSockJavaFd);
|
||||
if (readSock < 0) {
|
||||
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid read socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int writeSock = netjniutils::GetNativeFileDescriptor(env, writeSockJavaFd);
|
||||
if (writeSock < 0) {
|
||||
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid write socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 1. create a throwaway socket to reserve a file descriptor number
|
||||
int passedTunFd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
if (passedTunFd == -1) {
|
||||
throwIOException(env, "socket(ipv6/udp) for tun fd failed", errno);
|
||||
return -1;
|
||||
}
|
||||
int passedSockRead = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
if (passedSockRead == -1) {
|
||||
throwIOException(env, "socket(ipv6/udp) for read socket failed", errno);
|
||||
return -1;
|
||||
}
|
||||
int passedSockWrite = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
if (passedSockWrite == -1) {
|
||||
throwIOException(env, "socket(ipv6/udp) for write socket failed", errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// these are the FD we'll pass to clatd on the cli, so need it as a string
|
||||
char passedTunFdStr[INT32_STRLEN];
|
||||
char passedSockReadStr[INT32_STRLEN];
|
||||
char passedSockWriteStr[INT32_STRLEN];
|
||||
snprintf(passedTunFdStr, sizeof(passedTunFdStr), "%d", passedTunFd);
|
||||
snprintf(passedSockReadStr, sizeof(passedSockReadStr), "%d", passedSockRead);
|
||||
snprintf(passedSockWriteStr, sizeof(passedSockWriteStr), "%d", passedSockWrite);
|
||||
|
||||
// 2. we're going to use this as argv[0] to clatd to make ps output more useful
|
||||
std::string progname("clatd-");
|
||||
progname += ifaceStr.c_str();
|
||||
|
||||
// clang-format off
|
||||
const char* args[] = {progname.c_str(),
|
||||
"-i", ifaceStr.c_str(),
|
||||
"-p", pfx96Str.c_str(),
|
||||
"-4", v4Str.c_str(),
|
||||
"-6", v6Str.c_str(),
|
||||
"-t", passedTunFdStr,
|
||||
"-r", passedSockReadStr,
|
||||
"-w", passedSockWriteStr,
|
||||
nullptr};
|
||||
// clang-format on
|
||||
|
||||
// 3. register vfork requirement
|
||||
posix_spawnattr_t attr;
|
||||
if (int ret = posix_spawnattr_init(&attr)) {
|
||||
throwIOException(env, "posix_spawnattr_init failed", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: use android::base::ScopeGuard.
|
||||
if (int ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK)) {
|
||||
posix_spawnattr_destroy(&attr);
|
||||
throwIOException(env, "posix_spawnattr_setflags failed", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 4. register dup2() action: this is what 'clears' the CLOEXEC flag
|
||||
// on the tun fd that we want the child clatd process to inherit
|
||||
// (this will happen after the vfork, and before the execve)
|
||||
posix_spawn_file_actions_t fa;
|
||||
if (int ret = posix_spawn_file_actions_init(&fa)) {
|
||||
posix_spawnattr_destroy(&attr);
|
||||
throwIOException(env, "posix_spawn_file_actions_init failed", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (int ret = posix_spawn_file_actions_adddup2(&fa, tunFd, passedTunFd)) {
|
||||
posix_spawnattr_destroy(&attr);
|
||||
posix_spawn_file_actions_destroy(&fa);
|
||||
throwIOException(env, "posix_spawn_file_actions_adddup2 for tun fd failed", ret);
|
||||
return -1;
|
||||
}
|
||||
if (int ret = posix_spawn_file_actions_adddup2(&fa, readSock, passedSockRead)) {
|
||||
posix_spawnattr_destroy(&attr);
|
||||
posix_spawn_file_actions_destroy(&fa);
|
||||
throwIOException(env, "posix_spawn_file_actions_adddup2 for read socket failed", ret);
|
||||
return -1;
|
||||
}
|
||||
if (int ret = posix_spawn_file_actions_adddup2(&fa, writeSock, passedSockWrite)) {
|
||||
posix_spawnattr_destroy(&attr);
|
||||
posix_spawn_file_actions_destroy(&fa);
|
||||
throwIOException(env, "posix_spawn_file_actions_adddup2 for write socket failed", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 5. actually perform vfork/dup2/execve
|
||||
pid_t pid;
|
||||
if (int ret = posix_spawn(&pid, kClatdPath, &fa, &attr, (char* const*)args, nullptr)) {
|
||||
posix_spawnattr_destroy(&attr);
|
||||
posix_spawn_file_actions_destroy(&fa);
|
||||
throwIOException(env, "posix_spawn failed", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
posix_spawnattr_destroy(&attr);
|
||||
posix_spawn_file_actions_destroy(&fa);
|
||||
|
||||
// 5. Start BPF if any
|
||||
if (!net::clat::initMaps()) {
|
||||
net::clat::ClatdTracker tracker = {};
|
||||
if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
|
||||
@@ -300,19 +423,23 @@ static jint com_android_server_connectivity_ClatCoordinator_maybeStartBpf(
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // TODO: return forked clatd pid.
|
||||
return pid;
|
||||
}
|
||||
|
||||
// TODO: stop clatd and rename to .._stopClatd.
|
||||
static void com_android_server_connectivity_ClatCoordinator_maybeStopBpf(JNIEnv* env, jobject clazz,
|
||||
static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jobject clazz,
|
||||
jstring iface, jstring pfx96,
|
||||
jstring v4, jstring v6,
|
||||
jint pid /* unused */) {
|
||||
jint pid) {
|
||||
ScopedUtfChars ifaceStr(env, iface);
|
||||
ScopedUtfChars pfx96Str(env, pfx96);
|
||||
ScopedUtfChars v4Str(env, v4);
|
||||
ScopedUtfChars v6Str(env, v6);
|
||||
|
||||
if (pid <= 0) {
|
||||
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid pid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!net::clat::initMaps()) {
|
||||
net::clat::ClatdTracker tracker = {};
|
||||
if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
|
||||
@@ -320,6 +447,9 @@ static void com_android_server_connectivity_ClatCoordinator_maybeStopBpf(JNIEnv*
|
||||
net::clat::maybeStopBpf(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
kill(pid, SIGTERM);
|
||||
waitpid(pid, nullptr, 0); // Should we block in JNI?
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -344,13 +474,13 @@ static const JNINativeMethod gMethods[] = {
|
||||
(void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt},
|
||||
{"native_configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
|
||||
(void*)com_android_server_connectivity_ClatCoordinator_configurePacketSocket},
|
||||
{"native_maybeStartBpf",
|
||||
{"native_startClatd",
|
||||
"(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/lang/"
|
||||
"String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
|
||||
(void*)com_android_server_connectivity_ClatCoordinator_maybeStartBpf},
|
||||
{"native_maybeStopBpf",
|
||||
(void*)com_android_server_connectivity_ClatCoordinator_startClatd},
|
||||
{"native_stopClatd",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V",
|
||||
(void*)com_android_server_connectivity_ClatCoordinator_maybeStopBpf},
|
||||
(void*)com_android_server_connectivity_ClatCoordinator_stopClatd},
|
||||
};
|
||||
|
||||
int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
|
||||
|
||||
@@ -170,20 +170,20 @@ public class ClatCoordinator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe start bpf.
|
||||
* Start clatd.
|
||||
*/
|
||||
public int maybeStartBpf(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
|
||||
public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
|
||||
@NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96,
|
||||
@NonNull String v4, @NonNull String v6) throws IOException {
|
||||
return native_maybeStartBpf(tunfd, readsock6, writesock6, iface, pfx96, v4, v6);
|
||||
return native_startClatd(tunfd, readsock6, writesock6, iface, pfx96, v4, v6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe stop bpf.
|
||||
* Stop clatd.
|
||||
*/
|
||||
public void maybeStopBpf(String iface, String pfx96, String v4, String v6, int pid)
|
||||
public void stopClatd(String iface, String pfx96, String v4, String v6, int pid)
|
||||
throws IOException {
|
||||
native_maybeStopBpf(iface, pfx96, v4, v6, pid);
|
||||
native_stopClatd(iface, pfx96, v4, v6, pid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +219,9 @@ public class ClatCoordinator {
|
||||
public String clatStart(final String iface, final int netId,
|
||||
@NonNull final IpPrefix nat64Prefix)
|
||||
throws IOException {
|
||||
if (mIface != null || mPid != INVALID_PID) {
|
||||
throw new IOException("Clatd has started on " + mIface + " (pid " + mPid + ")");
|
||||
}
|
||||
if (nat64Prefix.getPrefixLength() != 96) {
|
||||
throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
|
||||
}
|
||||
@@ -254,6 +257,7 @@ public class ClatCoordinator {
|
||||
try {
|
||||
mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
|
||||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
tunFd.close();
|
||||
Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
|
||||
}
|
||||
|
||||
@@ -270,6 +274,7 @@ public class ClatCoordinator {
|
||||
try {
|
||||
mNetd.interfaceSetMtu(tunIface, mtu);
|
||||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
tunFd.close();
|
||||
throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
|
||||
}
|
||||
final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
|
||||
@@ -281,6 +286,7 @@ public class ClatCoordinator {
|
||||
try {
|
||||
mNetd.interfaceSetCfg(ifConfig);
|
||||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
tunFd.close();
|
||||
throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
|
||||
+ ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
|
||||
}
|
||||
@@ -290,11 +296,12 @@ public class ClatCoordinator {
|
||||
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
|
||||
// like to use ParcelFileDescriptor to manage file descriptor. 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) {
|
||||
tunFd.close();
|
||||
throw new IOException("Open packet socket failed: " + e);
|
||||
}
|
||||
|
||||
@@ -305,11 +312,16 @@ public class ClatCoordinator {
|
||||
// reason why we use jniOpenPacketSocket6().
|
||||
writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
|
||||
} catch (IOException e) {
|
||||
tunFd.close();
|
||||
readSock6.close();
|
||||
throw new IOException("Open raw socket failed: " + e);
|
||||
}
|
||||
|
||||
final int ifaceIndex = mDeps.getInterfaceIndex(iface);
|
||||
if (ifaceIndex == INVALID_IFINDEX) {
|
||||
tunFd.close();
|
||||
readSock6.close();
|
||||
writeSock6.close();
|
||||
throw new IOException("Fail to get interface index for interface " + iface);
|
||||
}
|
||||
|
||||
@@ -317,6 +329,9 @@ public class ClatCoordinator {
|
||||
try {
|
||||
mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex);
|
||||
} catch (IOException e) {
|
||||
tunFd.close();
|
||||
readSock6.close();
|
||||
writeSock6.close();
|
||||
throw new IOException("add anycast sockopt failed: " + e);
|
||||
}
|
||||
|
||||
@@ -324,31 +339,41 @@ public class ClatCoordinator {
|
||||
try {
|
||||
mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex);
|
||||
} catch (IOException e) {
|
||||
tunFd.close();
|
||||
readSock6.close();
|
||||
writeSock6.close();
|
||||
throw new IOException("configure packet socket failed: " + e);
|
||||
}
|
||||
|
||||
// [5] Maybe start bpf.
|
||||
// [5] Start clatd.
|
||||
try {
|
||||
mDeps.maybeStartBpf(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
|
||||
mPid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
|
||||
writeSock6.getFileDescriptor(), iface, pfx96, v4, v6);
|
||||
mIface = iface;
|
||||
mNat64Prefix = pfx96;
|
||||
mXlatLocalAddress4 = v4;
|
||||
mXlatLocalAddress6 = v6;
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Error start bpf on " + iface + ": " + e);
|
||||
throw new IOException("Error start clatd on " + iface + ": " + e);
|
||||
} finally {
|
||||
tunFd.close();
|
||||
readSock6.close();
|
||||
writeSock6.close();
|
||||
}
|
||||
|
||||
// TODO: start clatd and returns local xlat464 v6 address.
|
||||
return null;
|
||||
return v6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop clatd
|
||||
*/
|
||||
public void clatStop() throws IOException {
|
||||
mDeps.maybeStopBpf(mIface, mNat64Prefix, mXlatLocalAddress4, mXlatLocalAddress6,
|
||||
mPid /* unused */);
|
||||
if (mPid == INVALID_PID) {
|
||||
throw new IOException("Clatd has not started");
|
||||
}
|
||||
Log.i(TAG, "Stopping clatd pid=" + mPid + " on " + mIface);
|
||||
|
||||
mDeps.stopClatd(mIface, mNat64Prefix, mXlatLocalAddress4, mXlatLocalAddress6, mPid);
|
||||
// TODO: remove setIptablesDropRule
|
||||
|
||||
Log.i(TAG, "clatd on " + mIface + " stopped");
|
||||
@@ -357,6 +382,7 @@ public class ClatCoordinator {
|
||||
mNat64Prefix = null;
|
||||
mXlatLocalAddress4 = null;
|
||||
mXlatLocalAddress6 = null;
|
||||
mPid = INVALID_PID;
|
||||
}
|
||||
|
||||
private static native String native_selectIpv4Address(String v4addr, int prefixlen)
|
||||
@@ -372,9 +398,9 @@ public class ClatCoordinator {
|
||||
int ifindex) throws IOException;
|
||||
private static native void native_configurePacketSocket(FileDescriptor sock, String v6,
|
||||
int ifindex) throws IOException;
|
||||
private static native int native_maybeStartBpf(FileDescriptor tunfd, FileDescriptor readsock6,
|
||||
private static native int native_startClatd(FileDescriptor tunfd, FileDescriptor readsock6,
|
||||
FileDescriptor writesock6, String iface, String pfx96, String v4, String v6)
|
||||
throws IOException;
|
||||
private static native void native_maybeStopBpf(String iface, String pfx96, String v4,
|
||||
String v6, int pid) throws IOException;
|
||||
private static native void native_stopClatd(String iface, String pfx96, String v4, String v6,
|
||||
int pid) throws IOException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* 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 com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
|
||||
import static com.android.server.connectivity.ClatCoordinator.CLAT_MAX_MTU;
|
||||
import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_PREFIX_LEN;
|
||||
import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_STRING;
|
||||
import static com.android.testutils.MiscAsserts.assertThrows;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.argThat;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.net.INetd;
|
||||
import android.net.IpPrefix;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@SmallTest
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
public class ClatCoordinatorTest {
|
||||
private static final String BASE_IFACE = "test0";
|
||||
private static final String STACKED_IFACE = "v4-test0";
|
||||
private static final int BASE_IFINDEX = 1000;
|
||||
|
||||
private static final IpPrefix NAT64_IP_PREFIX = new IpPrefix("64:ff9b::/96");
|
||||
private static final String NAT64_PREFIX_STRING = "64:ff9b::";
|
||||
private static final int GOOGLE_DNS_4 = 0x08080808; // 8.8.8.8
|
||||
private static final int NETID = 42;
|
||||
|
||||
// The test fwmark means: PERMISSION_SYSTEM (0x2), protectedFromVpn: true,
|
||||
// explicitlySelected: true, netid: 42. For bit field structure definition, see union Fwmark in
|
||||
// system/netd/include/Fwmark.h
|
||||
private static final int MARK = 0xb002a;
|
||||
|
||||
private static final String XLAT_LOCAL_IPV4ADDR_STRING = "192.0.0.46";
|
||||
private static final String XLAT_LOCAL_IPV6ADDR_STRING = "2001:db8:0:b11::464";
|
||||
private static final int CLATD_PID = 10483;
|
||||
|
||||
private static final int TUN_FD = 534;
|
||||
private static final int RAW_SOCK_FD = 535;
|
||||
private static final int PACKET_SOCK_FD = 536;
|
||||
private static final ParcelFileDescriptor TUN_PFD = new ParcelFileDescriptor(
|
||||
new FileDescriptor());
|
||||
private static final ParcelFileDescriptor RAW_SOCK_PFD = new ParcelFileDescriptor(
|
||||
new FileDescriptor());
|
||||
private static final ParcelFileDescriptor PACKET_SOCK_PFD = new ParcelFileDescriptor(
|
||||
new FileDescriptor());
|
||||
|
||||
@Mock private INetd mNetd;
|
||||
@Spy private TestDependencies mDeps = new TestDependencies();
|
||||
|
||||
/**
|
||||
* The dependency injection class is used to mock the JNI functions and system functions
|
||||
* for clatd coordinator control plane. Note that any testing used JNI functions need to
|
||||
* be overridden to avoid calling native methods.
|
||||
*/
|
||||
protected class TestDependencies extends ClatCoordinator.Dependencies {
|
||||
/**
|
||||
* Get netd.
|
||||
*/
|
||||
@Override
|
||||
public INetd getNetd() {
|
||||
return mNetd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ParcelFileDescriptor#adoptFd(int).
|
||||
*/
|
||||
@Override
|
||||
public ParcelFileDescriptor adoptFd(int fd) {
|
||||
switch (fd) {
|
||||
case TUN_FD:
|
||||
return TUN_PFD;
|
||||
case RAW_SOCK_FD:
|
||||
return RAW_SOCK_PFD;
|
||||
case PACKET_SOCK_FD:
|
||||
return PACKET_SOCK_PFD;
|
||||
default:
|
||||
fail("unsupported arg: " + fd);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get interface index for a given interface.
|
||||
*/
|
||||
@Override
|
||||
public int getInterfaceIndex(String ifName) {
|
||||
if (BASE_IFACE.equals(ifName)) {
|
||||
return BASE_IFINDEX;
|
||||
}
|
||||
fail("unsupported arg: " + ifName);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tun interface for a given interface name.
|
||||
*/
|
||||
@Override
|
||||
public int createTunInterface(@NonNull String tuniface) throws IOException {
|
||||
if (STACKED_IFACE.equals(tuniface)) {
|
||||
return TUN_FD;
|
||||
}
|
||||
fail("unsupported arg: " + tuniface);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick an IPv4 address for clat.
|
||||
*/
|
||||
@Override
|
||||
public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
|
||||
throws IOException {
|
||||
if (INIT_V4ADDR_STRING.equals(v4addr) && INIT_V4ADDR_PREFIX_LEN == prefixlen) {
|
||||
return XLAT_LOCAL_IPV4ADDR_STRING;
|
||||
}
|
||||
fail("unsupported args: " + v4addr + ", " + prefixlen);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a checksum-neutral IID.
|
||||
*/
|
||||
@Override
|
||||
public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
|
||||
@NonNull String prefix64) throws IOException {
|
||||
if (BASE_IFACE.equals(iface) && XLAT_LOCAL_IPV4ADDR_STRING.equals(v4)
|
||||
&& NAT64_PREFIX_STRING.equals(prefix64)) {
|
||||
return XLAT_LOCAL_IPV6ADDR_STRING;
|
||||
}
|
||||
fail("unsupported args: " + iface + ", " + v4 + ", " + prefix64);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect MTU.
|
||||
*/
|
||||
@Override
|
||||
public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
|
||||
throws IOException {
|
||||
if (NAT64_PREFIX_STRING.equals(platSubnet) && GOOGLE_DNS_4 == platSuffix
|
||||
&& MARK == mark) {
|
||||
return ETHER_MTU;
|
||||
}
|
||||
fail("unsupported args: " + platSubnet + ", " + platSuffix + ", " + mark);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open IPv6 raw socket and set SO_MARK.
|
||||
*/
|
||||
@Override
|
||||
public int openRawSocket6(int mark) throws IOException {
|
||||
if (mark == MARK) {
|
||||
return RAW_SOCK_FD;
|
||||
}
|
||||
fail("unsupported arg: " + mark);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open packet socket.
|
||||
*/
|
||||
@Override
|
||||
public int openPacketSocket() throws IOException {
|
||||
// assume that open socket always successfully because there is no argument to check.
|
||||
return PACKET_SOCK_FD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add anycast setsockopt.
|
||||
*/
|
||||
@Override
|
||||
public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex)
|
||||
throws IOException {
|
||||
if (Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), sock)
|
||||
&& XLAT_LOCAL_IPV6ADDR_STRING.equals(v6)
|
||||
&& BASE_IFINDEX == ifindex) return;
|
||||
fail("unsupported args: " + sock + ", " + v6 + ", " + ifindex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure packet socket.
|
||||
*/
|
||||
@Override
|
||||
public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex)
|
||||
throws IOException {
|
||||
if (Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), sock)
|
||||
&& XLAT_LOCAL_IPV6ADDR_STRING.equals(v6)
|
||||
&& BASE_IFINDEX == ifindex) return;
|
||||
fail("unsupported args: " + sock + ", " + v6 + ", " + ifindex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start clatd.
|
||||
*/
|
||||
@Override
|
||||
public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
|
||||
@NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96,
|
||||
@NonNull String v4, @NonNull String v6) throws IOException {
|
||||
if (Objects.equals(TUN_PFD.getFileDescriptor(), tunfd)
|
||||
&& Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), readsock6)
|
||||
&& Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), writesock6)
|
||||
&& BASE_IFACE.equals(iface)
|
||||
&& NAT64_PREFIX_STRING.equals(pfx96)
|
||||
&& XLAT_LOCAL_IPV4ADDR_STRING.equals(v4)
|
||||
&& XLAT_LOCAL_IPV6ADDR_STRING.equals(v6)) {
|
||||
return CLATD_PID;
|
||||
}
|
||||
fail("unsupported args: " + tunfd + ", " + readsock6 + ", " + writesock6 + ", "
|
||||
+ ", " + iface + ", " + v4 + ", " + v6);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop clatd.
|
||||
*/
|
||||
public void stopClatd(@NonNull String iface, @NonNull String pfx96, @NonNull String v4,
|
||||
@NonNull String v6, int pid) throws IOException {
|
||||
if (pid == -1) {
|
||||
fail("unsupported arg: " + pid);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@NonNull
|
||||
private ClatCoordinator makeClatCoordinator() throws Exception {
|
||||
final ClatCoordinator coordinator = new ClatCoordinator(mDeps);
|
||||
return coordinator;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
private boolean assertContainsFlag(String[] flags, String match) {
|
||||
for (String flag : flags) {
|
||||
if (flag.equals(match)) return true;
|
||||
}
|
||||
fail("Missing flag: " + match);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartStopClatd() throws Exception {
|
||||
final ClatCoordinator coordinator = makeClatCoordinator();
|
||||
final InOrder inOrder = inOrder(mNetd, mDeps);
|
||||
clearInvocations(mNetd, mDeps);
|
||||
|
||||
// [1] Start clatd.
|
||||
final String addr6For464xlat = coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
|
||||
assertEquals(XLAT_LOCAL_IPV6ADDR_STRING, addr6For464xlat);
|
||||
|
||||
// Pick an IPv4 address.
|
||||
inOrder.verify(mDeps).selectIpv4Address(eq(INIT_V4ADDR_STRING),
|
||||
eq(INIT_V4ADDR_PREFIX_LEN));
|
||||
|
||||
// Generate a checksum-neutral IID.
|
||||
inOrder.verify(mDeps).generateIpv6Address(eq(BASE_IFACE),
|
||||
eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(NAT64_PREFIX_STRING));
|
||||
|
||||
// Open, configure and bring up the tun interface.
|
||||
inOrder.verify(mDeps).createTunInterface(eq(STACKED_IFACE));
|
||||
inOrder.verify(mDeps).adoptFd(eq(TUN_FD));
|
||||
inOrder.verify(mNetd).interfaceSetEnableIPv6(eq(STACKED_IFACE), eq(false /* enable */));
|
||||
inOrder.verify(mDeps).detectMtu(eq(NAT64_PREFIX_STRING), eq(GOOGLE_DNS_4), eq(MARK));
|
||||
inOrder.verify(mNetd).interfaceSetMtu(eq(STACKED_IFACE),
|
||||
eq(1472 /* ETHER_MTU(1500) - MTU_DELTA(28) */));
|
||||
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
|
||||
STACKED_IFACE.equals(cfg.ifName)
|
||||
&& XLAT_LOCAL_IPV4ADDR_STRING.equals(cfg.ipv4Addr)
|
||||
&& (32 == cfg.prefixLength)
|
||||
&& "".equals(cfg.hwAddr)
|
||||
&& assertContainsFlag(cfg.flags, IF_STATE_UP)));
|
||||
|
||||
// Open and configure 464xlat read/write sockets.
|
||||
inOrder.verify(mDeps).openPacketSocket();
|
||||
inOrder.verify(mDeps).adoptFd(eq(PACKET_SOCK_FD));
|
||||
inOrder.verify(mDeps).openRawSocket6(eq(MARK));
|
||||
inOrder.verify(mDeps).adoptFd(eq(RAW_SOCK_FD));
|
||||
inOrder.verify(mDeps).getInterfaceIndex(eq(BASE_IFACE));
|
||||
inOrder.verify(mDeps).addAnycastSetsockopt(
|
||||
argThat(fd -> Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), fd)),
|
||||
eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(BASE_IFINDEX));
|
||||
inOrder.verify(mDeps).configurePacketSocket(
|
||||
argThat(fd -> Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), fd)),
|
||||
eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(BASE_IFINDEX));
|
||||
|
||||
// Start clatd.
|
||||
inOrder.verify(mDeps).startClatd(
|
||||
argThat(fd -> Objects.equals(TUN_PFD.getFileDescriptor(), fd)),
|
||||
argThat(fd -> Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), fd)),
|
||||
argThat(fd -> Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), fd)),
|
||||
eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
|
||||
eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// [2] Start clatd again failed.
|
||||
assertThrows("java.io.IOException: Clatd has started on test0 (pid 10483)",
|
||||
IOException.class,
|
||||
() -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
|
||||
|
||||
// [3] Expect clatd to stop successfully.
|
||||
coordinator.clatStop();
|
||||
inOrder.verify(mDeps).stopClatd(eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
|
||||
eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(CLATD_PID));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// [4] Expect an IO exception while stopping a clatd that doesn't exist.
|
||||
assertThrows("java.io.IOException: Clatd has not started", IOException.class,
|
||||
() -> coordinator.clatStop());
|
||||
inOrder.verify(mDeps, never()).stopClatd(anyString(), anyString(), anyString(),
|
||||
anyString(), anyInt());
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFwmark() throws Exception {
|
||||
assertEquals(0xb0064, ClatCoordinator.getFwmark(100));
|
||||
assertEquals(0xb03e8, ClatCoordinator.getFwmark(1000));
|
||||
assertEquals(0xb2710, ClatCoordinator.getFwmark(10000));
|
||||
assertEquals(0xbffff, ClatCoordinator.getFwmark(65535));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdjustMtu() throws Exception {
|
||||
// Expected mtu is that IPV6_MIN_MTU(1280) minus MTU_DELTA(28).
|
||||
assertEquals(1252, ClatCoordinator.adjustMtu(-1 /* detect mtu failed */));
|
||||
assertEquals(1252, ClatCoordinator.adjustMtu(500));
|
||||
assertEquals(1252, ClatCoordinator.adjustMtu(1000));
|
||||
assertEquals(1252, ClatCoordinator.adjustMtu(1280));
|
||||
|
||||
// Expected mtu is that the detected mtu minus MTU_DELTA(28).
|
||||
assertEquals(1372, ClatCoordinator.adjustMtu(1400));
|
||||
assertEquals(1472, ClatCoordinator.adjustMtu(ETHER_MTU));
|
||||
assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU));
|
||||
|
||||
// Expected mtu is that CLAT_MAX_MTU(65536) minus MTU_DELTA(28).
|
||||
assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU + 1 /* over maximum mtu */));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user