Integrate port forwarding with setting page
Bug: 340126051
Bug: 375873420
Test: Manipulate port forwarding setting of terminal app
Change-Id: Ia925d8b5d6dcf7e78c886bcfb0d81d25ee5d9a5d
diff --git a/android/forwarder_host/src/forwarder_host.rs b/android/forwarder_host/src/forwarder_host.rs
index 26bae89..7496a02 100644
--- a/android/forwarder_host/src/forwarder_host.rs
+++ b/android/forwarder_host/src/forwarder_host.rs
@@ -28,7 +28,7 @@
use std::time::Duration;
use forwarder::forwarder::ForwarderSession;
-use jni::objects::{JObject, JValue};
+use jni::objects::{JIntArray, JObject, JValue};
use jni::sys::jint;
use jni::JNIEnv;
use log::{debug, error, info, warn};
@@ -45,11 +45,16 @@
static SHUTDOWN_EVT: LazyLock<EventFd> =
LazyLock::new(|| EventFd::new().expect("Could not create shutdown eventfd"));
+static UPDATE_EVT: LazyLock<EventFd> =
+ LazyLock::new(|| EventFd::new().expect("Could not create update eventfd"));
+
+static UPDATE_QUEUE: LazyLock<Arc<Mutex<VecDeque<u16>>>> =
+ LazyLock::new(|| Arc::new(Mutex::new(VecDeque::new())));
+
#[remain::sorted]
#[derive(Debug)]
enum Error {
BindVsock(io::Error),
- EventFdNew(nix::Error),
IncorrectCid(u32),
LaunchForwarderGuest(jni::errors::Error),
NoListenerForPort(u16),
@@ -62,7 +67,6 @@
TcpAccept(io::Error),
TcpListenerPort(io::Error),
UpdateEventRead(nix::Error),
- UpdateEventWrite(nix::Error),
VsockAccept(io::Error),
VsockAcceptTimeout,
VsockListenerPort(io::Error),
@@ -78,7 +82,6 @@
#[remain::sorted]
match self {
BindVsock(e) => write!(f, "failed to bind vsock: {}", e),
- EventFdNew(e) => write!(f, "failed to create eventfd: {}", e),
IncorrectCid(cid) => write!(f, "chunnel connection from unexpected cid {}", cid),
LaunchForwarderGuest(e) => write!(f, "failed to launch forwarder_guest {}", e),
NoListenerForPort(port) => write!(f, "could not find listener for port: {}", port),
@@ -93,7 +96,6 @@
write!(f, "failed to read local sockaddr for tcp listener: {}", e)
}
UpdateEventRead(e) => write!(f, "failed to read update eventfd: {}", e),
- UpdateEventWrite(e) => write!(f, "failed to write update eventfd: {}", e),
VsockAccept(e) => write!(f, "failed to accept vsock: {}", e),
VsockAcceptTimeout => write!(f, "timed out waiting for vsock connection"),
VsockListenerPort(e) => write!(f, "failed to get vsock listener port: {}", e),
@@ -101,12 +103,6 @@
}
}
-/// A TCP forwarding target. Uniquely identifies a listening port in a given container.
-struct TcpForwardTarget {
- pub port: u16,
- pub vsock_cid: u32,
-}
-
/// A tag that uniquely identifies a particular forwarding session. This has arbitrarily been
/// chosen as the fd of the local (TCP) socket.
type SessionTag = u32;
@@ -127,7 +123,6 @@
struct PortListeners {
tcp4_listener: TcpListener,
tcp6_listener: TcpListener,
- forward_target: TcpForwardTarget,
}
/// SocketFamily specifies whether a socket uses IPv4 or IPv6.
@@ -140,25 +135,18 @@
struct ForwarderSessions<'a> {
listening_ports: BTreeMap<u16, PortListeners>,
tcp4_forwarders: HashMap<SessionTag, ForwarderSession>,
- update_evt: EventFd,
- update_queue: Arc<Mutex<VecDeque<TcpForwardTarget>>>,
+ cid: u32,
jni_env: JNIEnv<'a>,
jni_cb: JObject<'a>,
}
impl<'a> ForwarderSessions<'a> {
/// Creates a new instance of ForwarderSessions.
- fn new(
- update_evt: EventFd,
- update_queue: Arc<Mutex<VecDeque<TcpForwardTarget>>>,
- jni_env: JNIEnv<'a>,
- jni_cb: JObject<'a>,
- ) -> Result<Self> {
+ fn new(cid: i32, jni_env: JNIEnv<'a>, jni_cb: JObject<'a>) -> Result<Self> {
Ok(ForwarderSessions {
listening_ports: BTreeMap::new(),
tcp4_forwarders: HashMap::new(),
- update_evt,
- update_queue,
+ cid: cid as u32,
jni_env,
jni_cb,
})
@@ -167,12 +155,11 @@
/// Adds or removes listeners based on the latest listening ports from the D-Bus thread.
fn process_update_queue(&mut self, poll_ctx: &PollContext<Token>) -> Result<()> {
// Unwrap of LockResult is customary.
- let mut update_queue = self.update_queue.lock().unwrap();
+ let mut update_queue = UPDATE_QUEUE.lock().unwrap();
let mut active_ports: BTreeSet<u16> = BTreeSet::new();
// Add any new listeners first.
- while let Some(target) = update_queue.pop_front() {
- let port = target.port;
+ while let Some(port) = update_queue.pop_front() {
// Ignore privileged ports.
if port < 1024 {
continue;
@@ -201,7 +188,7 @@
poll_ctx
.add(&tcp6_listener, Token::Ipv6Listener(port))
.map_err(Error::PollContextAdd)?;
- o.insert(PortListeners { tcp4_listener, tcp6_listener, forward_target: target });
+ o.insert(PortListeners { tcp4_listener, tcp6_listener });
}
active_ports.insert(port);
}
@@ -218,7 +205,7 @@
}
// Consume the eventfd.
- self.update_evt.read().map_err(Error::UpdateEventRead)?;
+ UPDATE_EVT.read().map_err(Error::UpdateEventRead)?;
Ok(())
}
@@ -240,12 +227,8 @@
// This session should be dropped if any of the PollContext setup fails. Since the only
// extant fds for the underlying sockets will be closed, they will be unregistered from
// epoll set automatically.
- let session = create_forwarder_session(
- listener,
- &port_listeners.forward_target,
- &mut self.jni_env,
- &self.jni_cb,
- )?;
+ let session =
+ create_forwarder_session(listener, self.cid, &mut self.jni_env, &self.jni_cb)?;
let tag = session.local_stream().as_raw_fd() as u32;
@@ -293,7 +276,7 @@
fn run(&mut self) -> Result<()> {
let poll_ctx: PollContext<Token> = PollContext::new().map_err(Error::PollContextNew)?;
- poll_ctx.add(&self.update_evt, Token::UpdatePorts).map_err(Error::PollContextAdd)?;
+ poll_ctx.add(&*UPDATE_EVT, Token::UpdatePorts).map_err(Error::PollContextAdd)?;
poll_ctx.add(&*SHUTDOWN_EVT, Token::Shutdown).map_err(Error::PollContextAdd)?;
loop {
@@ -340,7 +323,7 @@
/// Creates a forwarder session from a `listener` that has a pending connection to accept.
fn create_forwarder_session(
listener: &TcpListener,
- target: &TcpForwardTarget,
+ cid: u32,
jni_env: &mut JNIEnv,
jni_cb: &JObject,
) -> Result<ForwarderSession> {
@@ -376,7 +359,7 @@
Some(_) => {
let (vsock_stream, sockaddr) = vsock_listener.accept().map_err(Error::VsockAccept)?;
- if sockaddr.cid() != target.vsock_cid {
+ if sockaddr.cid() != cid {
Err(Error::IncorrectCid(sockaddr.cid()))
} else {
Ok(ForwarderSession::new(tcp_stream.into(), vsock_stream.into()))
@@ -386,33 +369,10 @@
}
}
-fn update_listening_ports(
- update_queue: &Arc<Mutex<VecDeque<TcpForwardTarget>>>,
- update_evt: &EventFd,
- cid: i32,
-) -> Result<()> {
- let mut update_queue = update_queue.lock().unwrap();
-
- // TODO(b/340126051): Bring listening ports from the guest.
- update_queue.push_back(TcpForwardTarget {
- port: 12345, /* Example value for testing */
- vsock_cid: cid as u32,
- });
-
- update_evt.write(1).map_err(Error::UpdateEventWrite)?;
- Ok(())
-}
-
// TODO(b/340126051): Host can receive opened ports from the guest.
fn run_forwarder_host(cid: i32, jni_env: JNIEnv, jni_cb: JObject) -> Result<()> {
debug!("Starting forwarder_host");
- let update_evt = EventFd::new().map_err(Error::EventFdNew)?;
- let update_queue = Arc::new(Mutex::new(VecDeque::new()));
-
- // TODO(b/340126051): Instead of one-time execution, bring port info with separated thread.
- update_listening_ports(&update_queue, &update_evt, cid)?;
-
- let mut sessions = ForwarderSessions::new(update_evt, update_queue, jni_env, jni_cb)?;
+ let mut sessions = ForwarderSessions::new(cid, jni_env, jni_cb)?;
sessions.run()
}
@@ -442,3 +402,22 @@
) {
SHUTDOWN_EVT.write(1).expect("Failed to write shutdown event FD");
}
+
+/// JNI function for updating listening ports.
+#[no_mangle]
+pub extern "C" fn Java_com_android_virtualization_vmlauncher_DebianServiceImpl_updateListeningPorts(
+ env: JNIEnv,
+ _class: JObject,
+ ports: JIntArray,
+) {
+ let length = env.get_array_length(&ports).expect("Failed to get length of port array");
+ let mut buf = vec![0; length as usize];
+ env.get_int_array_region(ports, 0, &mut buf).expect("Failed to get port array");
+
+ let mut update_queue = UPDATE_QUEUE.lock().unwrap();
+ update_queue.clear();
+ for port in buf {
+ update_queue.push_back(port.try_into().expect("Failed to add port into update queue"));
+ }
+ UPDATE_EVT.write(1).expect("failed to write update eventfd");
+}
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
index 61679f2..c64ff77 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
@@ -16,6 +16,8 @@
package com.android.virtualization.vmlauncher;
+import android.content.Context;
+import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.Keep;
@@ -28,17 +30,47 @@
import io.grpc.stub.StreamObserver;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
final class DebianServiceImpl extends DebianServiceGrpc.DebianServiceImplBase {
public static final String TAG = "DebianService";
+ private static final String PREFERENCE_FILE_KEY =
+ "com.android.virtualization.terminal.PREFERENCE_FILE_KEY";
+ private static final String PREFERENCE_FORWARDING_PORTS = "PREFERENCE_FORWARDING_PORTS";
+ private static final String PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX =
+ "PREFERENCE_FORWARDING_PORT_IS_ENABLED_";
+
+ private final Context mContext;
+ private final SharedPreferences mSharedPref;
+ private SharedPreferences.OnSharedPreferenceChangeListener mPortForwardingListener;
private final DebianServiceCallback mCallback;
+
static {
System.loadLibrary("forwarder_host_jni");
}
- protected DebianServiceImpl(DebianServiceCallback callback) {
+ DebianServiceImpl(Context context, DebianServiceCallback callback) {
super();
mCallback = callback;
+ mContext = context;
+ mSharedPref = mContext.getSharedPreferences(PREFERENCE_FILE_KEY, Context.MODE_PRIVATE);
+ // TODO(b/340126051): Instead of putting fixed value, receive active port list info from the
+ // guest.
+ if (!mSharedPref.contains(PREFERENCE_FORWARDING_PORTS)) {
+ SharedPreferences.Editor editor = mSharedPref.edit();
+ Set<String> ports = new HashSet<>();
+ for (int port = 8080; port < 8090; port++) {
+ ports.add(Integer.toString(port));
+ editor.putBoolean(
+ PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX + Integer.toString(port),
+ false);
+ }
+ editor.putStringSet(PREFERENCE_FORWARDING_PORTS, ports);
+ editor.apply();
+ }
}
@Override
@@ -55,6 +87,19 @@
public void openForwardingRequestQueue(
QueueOpeningRequest request, StreamObserver<ForwardingRequestItem> responseObserver) {
Log.d(DebianServiceImpl.TAG, "OpenForwardingRequestQueue");
+ mPortForwardingListener =
+ new SharedPreferences.OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
+ if (key.startsWith(PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX)
+ || key.equals(PREFERENCE_FORWARDING_PORTS)) {
+ updateListeningPorts();
+ }
+ }
+ };
+ mSharedPref.registerOnSharedPreferenceChangeListener(mPortForwardingListener);
+ updateListeningPorts();
runForwarderHost(request.getCid(), new ForwarderHostCallback(responseObserver));
responseObserver.onCompleted();
}
@@ -79,7 +124,32 @@
private static native void runForwarderHost(int cid, ForwarderHostCallback callback);
- public static native void terminateForwarderHost();
+ private static native void terminateForwarderHost();
+
+ void killForwarderHost() {
+ Log.d(DebianServiceImpl.TAG, "Stopping port forwarding");
+ if (mPortForwardingListener != null) {
+ mSharedPref.unregisterOnSharedPreferenceChangeListener(mPortForwardingListener);
+ terminateForwarderHost();
+ }
+ }
+
+ private static native void updateListeningPorts(int[] ports);
+
+ private void updateListeningPorts() {
+ updateListeningPorts(
+ mSharedPref
+ .getStringSet(PREFERENCE_FORWARDING_PORTS, Collections.emptySet())
+ .stream()
+ .filter(
+ port ->
+ mSharedPref.getBoolean(
+ PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX + port,
+ false))
+ .map(Integer::valueOf)
+ .mapToInt(Integer::intValue)
+ .toArray());
+ }
protected interface DebianServiceCallback {
void onIpAddressAvailable(String ipAddr);
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
index 2bd85e1..0c79d35 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
@@ -60,6 +60,7 @@
private VirtualMachine mVirtualMachine;
private ResultReceiver mResultReceiver;
private Server mServer;
+ private DebianServiceImpl mDebianService;
@Override
public IBinder onBind(Intent intent) {
@@ -129,6 +130,7 @@
@Override
public void onDestroy() {
super.onDestroy();
+ stopDebianServer();
if (mVirtualMachine != null) {
if (mVirtualMachine.getStatus() == VirtualMachine.STATUS_RUNNING) {
try {
@@ -142,7 +144,6 @@
mExecutorService = null;
mVirtualMachine = null;
}
- stopDebianServer();
}
private void startDebianServer() {
@@ -174,10 +175,11 @@
try {
// TODO(b/372666638): gRPC for java doesn't support vsock for now.
int port = 0;
+ mDebianService = new DebianServiceImpl(this, this);
mServer =
OkHttpServerBuilder.forPort(port, InsecureServerCredentials.create())
.intercept(interceptor)
- .addService(new DebianServiceImpl(this))
+ .addService(mDebianService)
.build()
.start();
} catch (IOException e) {
@@ -199,8 +201,10 @@
}
private void stopDebianServer() {
+ if (mDebianService != null) {
+ mDebianService.killForwarderHost();
+ }
if (mServer != null) {
- DebianServiceImpl.terminateForwarderHost();
mServer.shutdown();
}
}