Define DebianService for host-guest communication
1. Use TCP/IP socket for now
2. IP address reporter uses that
3. Refactoring: remove timeout for shell page because systemd unit for
ip addr reporter 'requires' ttyd
4. move source files for guest(debian) to guest dir
Bug: 372666638
Test: check if shell shows
Change-Id: Ida94a05de9998a06b4b5c7efebbae97c78617bf4
diff --git a/guest/forwarder_guest/Cargo.toml b/guest/forwarder_guest/Cargo.toml
new file mode 100644
index 0000000..65f57cf
--- /dev/null
+++ b/guest/forwarder_guest/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "forwarder_guest"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+clap = { version = "4.5.19", features = ["derive"] }
+forwarder = { path = "../../libs/libforwarder" }
+poll_token_derive = "0.1.0"
+remain = "0.2.14"
+vmm-sys-util = "0.12.1"
diff --git a/guest/forwarder_guest/src/main.rs b/guest/forwarder_guest/src/main.rs
new file mode 100644
index 0000000..6ebd4ef
--- /dev/null
+++ b/guest/forwarder_guest/src/main.rs
@@ -0,0 +1,123 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/chunnel/src/bin/chunnel.rs
+
+//! Guest-side stream socket forwarder
+
+use std::fmt;
+use std::result;
+
+use clap::Parser;
+use forwarder::forwarder::{ForwarderError, ForwarderSession};
+use forwarder::stream::{StreamSocket, StreamSocketError};
+use poll_token_derive::PollToken;
+use vmm_sys_util::poll::{PollContext, PollToken};
+
+#[remain::sorted]
+#[derive(Debug)]
+enum Error {
+ ConnectSocket(StreamSocketError),
+ Forward(ForwarderError),
+ PollContextAdd(vmm_sys_util::errno::Error),
+ PollContextDelete(vmm_sys_util::errno::Error),
+ PollContextNew(vmm_sys_util::errno::Error),
+ PollWait(vmm_sys_util::errno::Error),
+}
+
+type Result<T> = result::Result<T, Error>;
+
+impl fmt::Display for Error {
+ #[remain::check]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Error::*;
+
+ #[remain::sorted]
+ match self {
+ ConnectSocket(e) => write!(f, "failed to connect socket: {}", e),
+ Forward(e) => write!(f, "failed to forward traffic: {}", e),
+ PollContextAdd(e) => write!(f, "failed to add fd to poll context: {}", e),
+ PollContextDelete(e) => write!(f, "failed to delete fd from poll context: {}", e),
+ PollContextNew(e) => write!(f, "failed to create poll context: {}", e),
+ PollWait(e) => write!(f, "failed to wait for poll: {}", e),
+ }
+ }
+}
+
+fn run_forwarder(local_stream: StreamSocket, remote_stream: StreamSocket) -> Result<()> {
+ #[derive(PollToken)]
+ enum Token {
+ LocalStreamReadable,
+ RemoteStreamReadable,
+ }
+ let poll_ctx: PollContext<Token> = PollContext::new().map_err(Error::PollContextNew)?;
+ poll_ctx.add(&local_stream, Token::LocalStreamReadable).map_err(Error::PollContextAdd)?;
+ poll_ctx.add(&remote_stream, Token::RemoteStreamReadable).map_err(Error::PollContextAdd)?;
+
+ let mut forwarder = ForwarderSession::new(local_stream, remote_stream);
+
+ loop {
+ let events = poll_ctx.wait().map_err(Error::PollWait)?;
+
+ for event in events.iter_readable() {
+ match event.token() {
+ Token::LocalStreamReadable => {
+ let shutdown = forwarder.forward_from_local().map_err(Error::Forward)?;
+ if shutdown {
+ poll_ctx
+ .delete(forwarder.local_stream())
+ .map_err(Error::PollContextDelete)?;
+ }
+ }
+ Token::RemoteStreamReadable => {
+ let shutdown = forwarder.forward_from_remote().map_err(Error::Forward)?;
+ if shutdown {
+ poll_ctx
+ .delete(forwarder.remote_stream())
+ .map_err(Error::PollContextDelete)?;
+ }
+ }
+ }
+ }
+ if forwarder.is_shut_down() {
+ return Ok(());
+ }
+ }
+}
+
+#[derive(Parser)]
+/// Flags for running command
+pub struct Args {
+ /// Local socket address
+ #[arg(long)]
+ #[arg(alias = "local")]
+ local_sockaddr: String,
+
+ /// Remote socket address
+ #[arg(long)]
+ #[arg(alias = "remote")]
+ remote_sockaddr: String,
+}
+
+// TODO(b/370897694): Support forwarding for datagram socket
+fn main() -> Result<()> {
+ let args = Args::parse();
+
+ let local_stream = StreamSocket::connect(&args.local_sockaddr).map_err(Error::ConnectSocket)?;
+ let remote_stream =
+ StreamSocket::connect(&args.remote_sockaddr).map_err(Error::ConnectSocket)?;
+
+ run_forwarder(local_stream, remote_stream)
+}
diff --git a/guest/ip_addr_reporter/.gitignore b/guest/ip_addr_reporter/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/guest/ip_addr_reporter/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/guest/ip_addr_reporter/Cargo.toml b/guest/ip_addr_reporter/Cargo.toml
new file mode 100644
index 0000000..e255eaf
--- /dev/null
+++ b/guest/ip_addr_reporter/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "ip_addr_reporter"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+netdev = "0.31.0"
+prost = "0.13.3"
+tokio = { version = "1.40.0", features = ["rt-multi-thread"] }
+tonic = "0.12.3"
+
+[build-dependencies]
+tonic-build = "0.12.3"
diff --git a/guest/ip_addr_reporter/build.rs b/guest/ip_addr_reporter/build.rs
new file mode 100644
index 0000000..e3939d4
--- /dev/null
+++ b/guest/ip_addr_reporter/build.rs
@@ -0,0 +1,7 @@
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let proto_file = "../../libs/debian_service/proto/DebianService.proto";
+
+ tonic_build::compile_protos(proto_file).unwrap();
+
+ Ok(())
+}
diff --git a/guest/ip_addr_reporter/src/main.rs b/guest/ip_addr_reporter/src/main.rs
new file mode 100644
index 0000000..5784a83
--- /dev/null
+++ b/guest/ip_addr_reporter/src/main.rs
@@ -0,0 +1,26 @@
+use api::debian_service_client::DebianServiceClient;
+use api::IpAddr;
+
+pub mod api {
+ tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
+}
+
+#[tokio::main]
+async fn main() -> Result<(), String> {
+ let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
+ let ip_addr = netdev::get_default_interface()?.ipv4[0].addr();
+ const PORT: i32 = 12000;
+
+ let server_addr = format!("http://{}:{}", gateway_ip_addr.to_string(), PORT);
+
+ println!("local ip addr: {}", ip_addr.to_string());
+ println!("coonect to grpc server {}", server_addr);
+
+ let mut client = DebianServiceClient::connect(server_addr).await.map_err(|e| e.to_string())?;
+
+ let request = tonic::Request::new(IpAddr { addr: ip_addr.to_string() });
+
+ let response = client.report_vm_ip_addr(request).await.map_err(|e| e.to_string())?;
+ println!("response from server: {:?}", response);
+ Ok(())
+}
diff --git a/guest/port_listener/build.sh b/guest/port_listener/build.sh
new file mode 100755
index 0000000..a1d0205
--- /dev/null
+++ b/guest/port_listener/build.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+set -e
+
+check_sudo() {
+ if [ "$EUID" -ne 0 ]; then
+ echo "Please run as root."
+ exit
+ fi
+}
+
+install_prerequisites() {
+ apt update
+ apt install --no-install-recommends --assume-yes \
+ bpftool \
+ clang \
+ libbpf-dev \
+ libgoogle-glog-dev \
+ libstdc++-14-dev
+}
+
+build_port_listener() {
+ cp $(dirname $0)/src/* ${workdir}
+ out_dir=${PWD}
+ pushd ${workdir}
+ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
+ clang \
+ -O2 \
+ -Wall \
+ -target bpf \
+ -g \
+ -c listen_tracker.ebpf.c \
+ -o listen_tracker.ebpf.o
+ bpftool gen skeleton listen_tracker.ebpf.o > listen_tracker.skel.h
+ clang++ \
+ -O2 \
+ -Wall \
+ -lbpf \
+ -lglog \
+ -o port_listener \
+ main.cc
+ cp port_listener ${out_dir}
+ popd
+}
+
+clean_up() {
+ rm -rf ${workdir}
+}
+trap clean_up EXIT
+workdir=$(mktemp -d)
+
+check_sudo
+install_prerequisites
+build_port_listener
diff --git a/guest/port_listener/src/common.h b/guest/port_listener/src/common.h
new file mode 100644
index 0000000..d6e507c
--- /dev/null
+++ b/guest/port_listener/src/common.h
@@ -0,0 +1,31 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/port_listener/common.h
+
+#ifndef VM_TOOLS_PORT_LISTENER_COMMON_H_
+#define VM_TOOLS_PORT_LISTENER_COMMON_H_
+
+enum State {
+ kPortListenerUp,
+ kPortListenerDown,
+};
+
+struct event {
+ enum State state;
+ uint16_t port;
+};
+
+#endif // VM_TOOLS_PORT_LISTENER_COMMON_H_
diff --git a/guest/port_listener/src/listen_tracker.ebpf.c b/guest/port_listener/src/listen_tracker.ebpf.c
new file mode 100644
index 0000000..9e98aad
--- /dev/null
+++ b/guest/port_listener/src/listen_tracker.ebpf.c
@@ -0,0 +1,80 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/port_listener/listen_tracker.ebpf.c
+
+// bpf_helpers.h uses types defined here
+#include <bpf/bpf_helpers.h>
+
+#include "common.h"
+#include "vmlinux.h"
+
+// For some reason 6.1 doesn't include these symbols in the debug build
+// so they don't get included in vmlinux.h. These features have existed since
+// well before 6.1.
+#define BPF_F_NO_PREALLOC (1U << 0)
+#define BPF_ANY 0
+
+struct {
+ __uint(type, BPF_MAP_TYPE_RINGBUF);
+ __uint(max_entries, 1 << 24);
+} events SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, struct sock*);
+ __type(value, __u8);
+ __uint(max_entries, 65535);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+} sockmap SEC(".maps");
+
+const __u8 set_value = 0;
+
+SEC("tp/sock/inet_sock_set_state")
+int tracepoint_inet_sock_set_state(struct trace_event_raw_inet_sock_set_state* ctx) {
+ // We don't support anything other than TCP.
+ if (ctx->protocol != IPPROTO_TCP) {
+ return 0;
+ }
+ struct sock* sk = (struct sock*)ctx->skaddr;
+ // If we're transitioning away from LISTEN but we don't know about this
+ // socket yet then don't report anything.
+ if (ctx->oldstate == BPF_TCP_LISTEN && bpf_map_lookup_elem(&sockmap, &sk) == NULL) {
+ return 0;
+ }
+ // If we aren't transitioning to or from TCP_LISTEN then we don't care.
+ if (ctx->newstate != BPF_TCP_LISTEN && ctx->oldstate != BPF_TCP_LISTEN) {
+ return 0;
+ }
+
+ struct event* ev;
+ ev = bpf_ringbuf_reserve(&events, sizeof(*ev), 0);
+ if (!ev) {
+ return 0;
+ }
+ ev->port = ctx->sport;
+
+ if (ctx->newstate == BPF_TCP_LISTEN) {
+ bpf_map_update_elem(&sockmap, &sk, &set_value, BPF_ANY);
+ ev->state = kPortListenerUp;
+ }
+ if (ctx->oldstate == BPF_TCP_LISTEN) {
+ bpf_map_delete_elem(&sockmap, &sk);
+ ev->state = kPortListenerDown;
+ }
+ bpf_ringbuf_submit(ev, 0);
+
+ return 0;
+}
diff --git a/guest/port_listener/src/main.cc b/guest/port_listener/src/main.cc
new file mode 100644
index 0000000..1caceaf
--- /dev/null
+++ b/guest/port_listener/src/main.cc
@@ -0,0 +1,167 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/port_listener/main.cc
+
+#include <bpf/libbpf.h>
+#include <bpf/libbpf_legacy.h>
+#include <glog/logging.h>
+#include <linux/vm_sockets.h> // Needs to come after sys/socket.h
+#include <sys/socket.h>
+
+#include <memory>
+#include <unordered_map>
+
+#include "common.h"
+#include "listen_tracker.skel.h"
+
+typedef std::unordered_map<int, int> port_usage_map;
+
+namespace port_listener {
+namespace {
+
+int HandleEvent(void* ctx, void* const data, size_t size) {
+ port_usage_map* map = reinterpret_cast<port_usage_map*>(ctx);
+ const struct event* ev = (struct event*)data;
+
+ switch (ev->state) {
+ case kPortListenerUp:
+ (*map)[ev->port]++;
+ break;
+
+ case kPortListenerDown:
+ if ((*map)[ev->port] > 0) {
+ (*map)[ev->port]--;
+ } else {
+ LOG(INFO) << "Received down event while port count was 0; ignoring";
+ }
+
+ break;
+
+ default:
+ LOG(ERROR) << "Unknown event state " << ev->state;
+ }
+
+ LOG(INFO) << "Listen event: port=" << ev->port << " state=" << ev->state;
+
+ return 0;
+}
+
+typedef std::unique_ptr<struct ring_buffer, decltype(&ring_buffer__free)> ring_buffer_ptr;
+typedef std::unique_ptr<listen_tracker_ebpf, decltype(&listen_tracker_ebpf__destroy)>
+ listen_tracker_ptr;
+
+// BPFProgram tracks the state and resources of the listen_tracker BPF program.
+class BPFProgram {
+public:
+ // Default movable but not copyable.
+ BPFProgram(BPFProgram&& other) = default;
+ BPFProgram(const BPFProgram& other) = delete;
+ BPFProgram& operator=(BPFProgram&& other) = default;
+ BPFProgram& operator=(const BPFProgram& other) = delete;
+
+ // Load loads the listen_tracker BPF program and prepares it for polling. On
+ // error nullptr is returned.
+ static std::unique_ptr<BPFProgram> Load() {
+ auto* skel = listen_tracker_ebpf__open();
+ if (!skel) {
+ PLOG(ERROR) << "Failed to open listen_tracker BPF skeleton";
+ return nullptr;
+ }
+ listen_tracker_ptr skeleton(skel, listen_tracker_ebpf__destroy);
+
+ int err = listen_tracker_ebpf__load(skeleton.get());
+ if (err) {
+ PLOG(ERROR) << "Failed to load listen_tracker BPF program";
+ return nullptr;
+ }
+
+ auto map = std::make_unique<port_usage_map>();
+ auto* rb = ring_buffer__new(bpf_map__fd(skel->maps.events), HandleEvent, map.get(), NULL);
+ if (!rb) {
+ PLOG(ERROR) << "Failed to open ring buffer for listen_tracker";
+ return nullptr;
+ }
+ ring_buffer_ptr ringbuf(rb, ring_buffer__free);
+
+ err = listen_tracker_ebpf__attach(skeleton.get());
+ if (err) {
+ PLOG(ERROR) << "Failed to attach listen_tracker";
+ return nullptr;
+ }
+
+ return std::unique_ptr<BPFProgram>(
+ new BPFProgram(std::move(skeleton), std::move(ringbuf), std::move(map)));
+ }
+
+ // Poll waits for the listen_tracker BPF program to post a new event to the
+ // ring buffer. BPFProgram handles integrating this new event into the
+ // port_usage map and callers should consult port_usage() after Poll returns
+ // for the latest data.
+ const bool Poll() {
+ int err = ring_buffer__poll(rb_.get(), -1);
+ if (err < 0) {
+ LOG(ERROR) << "Error polling ring buffer ret=" << err;
+ return false;
+ }
+
+ return true;
+ }
+
+ const port_usage_map& port_usage() { return *port_usage_; }
+
+private:
+ BPFProgram(listen_tracker_ptr&& skeleton, ring_buffer_ptr&& rb,
+ std::unique_ptr<port_usage_map>&& port_usage)
+ : skeleton_(std::move(skeleton)),
+ rb_(std::move(rb)),
+ port_usage_(std::move(port_usage)) {}
+
+ listen_tracker_ptr skeleton_;
+ ring_buffer_ptr rb_;
+ std::unique_ptr<port_usage_map> port_usage_;
+};
+
+} // namespace
+} // namespace port_listener
+
+int main(int argc, char** argv) {
+ google::InitGoogleLogging(argv[0]);
+ libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
+
+ // Load our BPF program.
+ auto program = port_listener::BPFProgram::Load();
+ if (program == nullptr) {
+ LOG(ERROR) << "Failed to load BPF program";
+ return EXIT_FAILURE;
+ }
+
+ // main loop: poll for listen updates
+ for (;;) {
+ if (!program->Poll()) {
+ LOG(ERROR) << "Failure while polling BPF program";
+ return EXIT_FAILURE;
+ }
+ // port_usage will be updated with the latest usage data
+
+ for (auto it : program->port_usage()) {
+ if (it.second <= 0) {
+ continue;
+ }
+ // TODO(b/340126051): Add listening TCP4 ports.
+ }
+ // TODO(b/340126051): Notify port information to the guest agent.
+ }
+}