AVF: allow FD -> IBinder.
Okay, so before we have two APIs:
- connectToVsockServer "AB"
- connectVsock "A"
This adds:
- binderFromPreconnectedClient "B"
That is:
- connectToVsockServer = connectVsock + binderFromPreconnectedClient
Normally, in the same process, you do all of this, but to
pass a connection to another process, imagine
processes "Q -> R -> S", where:
- Q is client
- R is VM creator
- S is VM
Then Q can call binderFromPreconnectedClient, and it can
implement the provider by calling an API on R which will
return the results of R calling connectVsock.
Non-binder use of connectVsock is not recommended.
Bugs: me and without this, extra hops cost performannce
Test: atest MicrodroidTestApp - several tests pass, so letting TH run
the rest
Change-Id: I670243714bbb6167a1fdae2bb898b1f5a9d01f5f
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
index 5f634ef..3927a98 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
@@ -1960,8 +1960,43 @@
}
}
- @Nullable
- private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
+ /**
+ * Abstracts away the task of creating a vsock connection. Normally, in the same process, you'll
+ * make the connection and then promote it to a binder as part of the same API call, but if you
+ * want to pass a connection to another process first, before establishing the RPC binder
+ * connection, you may implement this method by getting a vsock connection from another process.
+ *
+ * <p>It is recommended to convert other types of exceptions (e.g. remote exceptions) to
+ * VirtualMachineException, so that all connection failures will be visible under the same type
+ * of exception.
+ *
+ * @hide
+ */
+ public interface VsockConnectionProvider {
+ @NonNull
+ public ParcelFileDescriptor addConnection() throws VirtualMachineException;
+ }
+
+ /**
+ * Class to make it easy to use JNI, without needing PFD and other classes.
+ *
+ * @hide
+ */
+ private static class NativeProviderWrapper {
+ private VsockConnectionProvider mProvider = null;
+
+ // ensures last FD is owned until get is called again
+ private ParcelFileDescriptor mMoreLifetime = null;
+
+ public NativeProviderWrapper(VsockConnectionProvider provider) {
+ mProvider = provider;
+ }
+
+ int connect() throws VirtualMachineException {
+ mMoreLifetime = mProvider.addConnection();
+ return mMoreLifetime.getFileDescriptor().getInt$();
+ }
+ }
/**
* Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
@@ -1981,15 +2016,36 @@
public IBinder connectToVsockServer(
@IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
throws VirtualMachineException {
+ VsockConnectionProvider provider =
+ new VsockConnectionProvider() {
+ @Override
+ public ParcelFileDescriptor addConnection() throws VirtualMachineException {
+ return connectVsock(port);
+ }
+ };
+ return binderFromPreconnectedClient(provider);
+ }
- synchronized (mLock) {
- IBinder iBinder =
- nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port));
- if (iBinder == null) {
- throw new VirtualMachineException("Failed to connect to vsock server");
- }
- return iBinder;
+ @Nullable
+ private static native IBinder nativeBinderFromPreconnectedClient(
+ NativeProviderWrapper provider);
+
+ /**
+ * Convert existing vsock connection to a binder connection.
+ *
+ * <p>connectToVsockServer = connectToVsock + binderFromPreconnectedClient
+ *
+ * @hide
+ */
+ @WorkerThread
+ @NonNull
+ public static IBinder binderFromPreconnectedClient(@NonNull VsockConnectionProvider provider)
+ throws VirtualMachineException {
+ IBinder binder = nativeBinderFromPreconnectedClient(new NativeProviderWrapper(provider));
+ if (binder == null) {
+ throw new VirtualMachineException("Failed to connect to vsock server");
}
+ return binder;
}
/**
diff --git a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
index 3f1d8a0..67a4716 100644
--- a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -48,32 +48,30 @@
} // namespace
extern "C" JNIEXPORT jobject JNICALL
-Java_android_system_virtualmachine_VirtualMachine_nativeConnectToVsockServer(
- JNIEnv *env, [[maybe_unused]] jclass clazz, jobject vmBinder, jint port) {
- using aidl::android::system::virtualizationservice::IVirtualMachine;
- using ndk::ScopedFileDescriptor;
- using ndk::SpAIBinder;
+Java_android_system_virtualmachine_VirtualMachine_nativeBinderFromPreconnectedClient(
+ [[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jobject provider) {
+ ScopedLocalRef<jclass> callback_class(env, env->GetObjectClass(provider));
+ jmethodID mid = env->GetMethodID(callback_class.get(), "connect", "()I");
+ LOG_ALWAYS_FATAL_IF(mid == nullptr, "Could not find method");
- auto vm = IVirtualMachine::fromBinder(SpAIBinder{AIBinder_fromJavaBinder(env, vmBinder)});
+ // TODO(b/398890208): make this memory owned by the connection
+ struct State {
+ JNIEnv *mEnv;
+ jobject mProvider;
+ jmethodID mMid;
+ } state;
- std::tuple args{env, vm.get(), port};
- using Args = decltype(args);
+ state.mEnv = env;
+ state.mProvider = provider;
+ state.mMid = mid;
- auto requestFunc = [](void *param) {
- auto [env, vm, port] = *static_cast<Args *>(param);
-
- ScopedFileDescriptor fd;
- if (auto status = vm->connectVsock(port, &fd); !status.isOk()) {
- env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
- ("Failed to connect vsock: " + status.getDescription()).c_str());
- return -1;
- }
-
- // take ownership
- int ret = fd.get();
- *fd.getR() = -1;
-
- return ret;
+ using RequestFun = int (*)(void *);
+ RequestFun requestFunc = [](void *param) -> int {
+ State *state = reinterpret_cast<State *>(param);
+ int ownedFd = state->mEnv->CallIntMethod(state->mProvider, state->mMid);
+ // FD is owned by PFD in Java layer, need to dupe it so that
+ // ARpcSession_setupPreconnectedClient can take ownership when it calls unique_fd internally
+ return fcntl(ownedFd, F_DUPFD_CLOEXEC, 0);
};
RpcSessionHandle session;
@@ -82,7 +80,7 @@
// want too many. The number 1 is chosen after some discussion, and to match
// the server-side default (mMaxThreads on RpcServer).
ARpcSession_setMaxIncomingThreads(session.get(), 1);
- auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args);
+ auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &state);
return AIBinder_toJavaBinder(env, client);
}