blob: 9f586a350f97e7d04341b0533cc681bc6708945e [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::vm_grpc::proto::concierge_service::{
GetVmInfoRequest, GetVmInfoResponse, VmStartedSignal, VmStoppedSignal,
};
use crate::vm_grpc::vm_grpc_client::VmGrpcClient;
use crate::vm_grpc::vm_grpc_server::VmGrpcServer;
use anyhow::{bail, Result};
use dbus::channel::MatchingReceiver;
use dbus::message::MatchRule;
use dbus::nonblock::Proxy;
use dbus::nonblock::SyncConnection;
use dbus::Message;
use libchromeos::sys::{info, warn};
use once_cell::sync::Lazy;
use protobuf::CodedInputStream;
use protobuf::Message as protoMessage;
use std::path::Path;
use std::sync::atomic::AtomicI64;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
// VSOCK port to use for accepting GRPC socket connections.
const RESOURCED_GRPC_SERVER_PORT: u16 = 5551;
// VSOCK port where resourced can connect a client.
const GUEST_VM_GRPC_SERVER_PORT: u16 = 5553;
// Timeout in ms for making a dbus method calls to other services.
const DBUS_PROXY_TIMEOUT_MS: u64 = 1000;
// VSOCK CID for accepting any serverside connection.
const CID_ANY: i16 = -1;
// Name for the borealis VM.
const BOREALIS_VM_NAME: &str = "borealis";
static VM_GRPC_SERVER_STOP_REQ: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
pub fn vm_server_stop_requested() -> bool {
match VM_GRPC_SERVER_STOP_REQ.lock() {
Ok(data) => *data,
Err(_) => {
warn!("Could not acquire lock for VM_GRPC_SERVER_STOP_REQ");
false
}
}
}
fn request_vm_server_stop() -> Result<()> {
match VM_GRPC_SERVER_STOP_REQ.lock() {
Ok(mut data) => {
*data = true;
}
Err(_) => bail!("GRPC server stop request failed"),
};
Ok(())
}
pub fn vm_server_stop_req_reset() {
match VM_GRPC_SERVER_STOP_REQ.lock() {
Ok(mut data) => *data = false,
Err(_) => {
warn!("Could not reset grpc_server_stop_request.");
warn!("Grpc server may not restart in future requests.");
}
}
}
fn vm_grpc_init(borealis_cid: i16) -> Result<VmGrpcServer> {
// if server is already with a different cid, restart it with new cid.
// client is expected to timeout and drop
let root = Path::new("/");
let pkt_tx_interval = Arc::new(AtomicI64::new(-1));
let pkt_tx_interval_clone = pkt_tx_interval.clone();
let server = VmGrpcServer::run(CID_ANY, RESOURCED_GRPC_SERVER_PORT, root, pkt_tx_interval)?;
VmGrpcClient::run(
borealis_cid,
GUEST_VM_GRPC_SERVER_PORT,
root,
pkt_tx_interval_clone,
)?;
Ok(server)
}
/// Extracts the CID for the borealis VM from the a VmStarterSignal D-bus message.
///
/// Function consumes a VmStarterSignal and extracts the vm_cid if the vm_name is borealis.
/// conn`: pre-initialized non-blocking dbus connection object.
///
/// # Return: Result object with userhash embedded.
fn get_borealis_cid_from_vm_started_signal(msg: &Message) -> Result<i16> {
let raw_buffer: Vec<u8> = msg.read1()?;
let input = &mut CodedInputStream::from_bytes(&raw_buffer);
let mut borealis_vm = VmStartedSignal::new();
borealis_vm.merge_from(input)?;
let name_vm = borealis_vm.get_name();
if name_vm != BOREALIS_VM_NAME {
bail!("ignoring VmStartedSIgnal for {}.", name_vm)
}
let borealis_cid = borealis_vm.get_vm_info().get_cid() as i16;
Ok(borealis_cid)
}
fn vm_stopped_signal_is_for_borealis(msg: &Message) -> Result<bool> {
let byte_array: Vec<u8> = msg.read1()?;
let mut vm_stopped_payload = VmStoppedSignal::new();
vm_stopped_payload.merge_from_bytes(&byte_array)?;
Ok(vm_stopped_payload.get_name() == BOREALIS_VM_NAME)
}
/// Function to retrieve cryptohome.
///
/// `conn`: pre-initialized non-blocking dbus connection object.
///
/// # Return: Result object with userhash embedded.
async fn retrieve_primary_session(conn: Arc<SyncConnection>) -> Result<String> {
let session_proxy = Proxy::new(
"org.chromium.SessionManager",
"/org/chromium/SessionManager",
Duration::from_millis(DBUS_PROXY_TIMEOUT_MS),
conn,
);
let (_user_name, session_id): (String, String) = session_proxy
.method_call(
"org.chromium.SessionManagerInterface",
"RetrievePrimarySession",
(),
)
.await?;
Ok(session_id)
}
/// Function to retrieve borealis CID.
///
/// This function makes a dbus call to session_manager to retrieve the cryptohome value.
/// The cryptohome value is used to call concierge for borealis vm_info, which contains
/// the borealis cid.
/// Each dbus call has a 1 second timeout, total blocking time for this function is 2 seconds.
///
/// `conn`: pre-initialized non-blocking dbus connection object.
///
/// # Return: Result object with borealis CID embedded.
async fn get_borealis_cid_from_concierge_async(conn: Arc<SyncConnection>) -> Result<i16> {
let mut vm_info_request = GetVmInfoRequest::default();
vm_info_request.set_name(BOREALIS_VM_NAME.to_string());
vm_info_request.set_owner_id(retrieve_primary_session(conn.clone()).await?);
let concierge_proxy = Proxy::new(
"org.chromium.VmConcierge",
"/org/chromium/VmConcierge",
Duration::from_millis(DBUS_PROXY_TIMEOUT_MS),
conn,
);
let concierge_resp: (Vec<u8>,) = concierge_proxy
.method_call(
"org.chromium.VmConcierge",
"GetVmInfo",
(vm_info_request.write_to_bytes()?,),
)
.await?;
let dbus_resp_bytes = &mut CodedInputStream::from_bytes(&(concierge_resp.0));
let mut get_vm_info_resp = GetVmInfoResponse::new();
get_vm_info_resp.merge_from(dbus_resp_bytes)?;
let cid = get_vm_info_resp.get_vm_info().get_cid() as i16;
// CID values [-1,0,1,2] are reserved. Sanity check the received CID.
if cid > 2 {
Ok(cid)
} else {
bail!("Could not get a valid CID");
}
}
/// Registers for dbus signals.
///
/// This function registers callbacks for `VmStoppedSignal` and `VmStartedSignal` from concierge.
/// At VM start, it will retrieve the VM CID and start a new grpc server/client pair.
/// On VM stop, it will teardown the server. Client will independently teardown after 1s of
/// inactivity.
///
/// `conn`: pre-initialized non-blocking dbus connection object.
///
/// # Return: Emtpty Result object.
pub async fn register_dbus_hooks_async(conn: Arc<SyncConnection>) -> Result<()> {
let vm_stopped_rule = MatchRule::new_signal("org.chromium.VmConcierge", "VmStoppedSignal");
let vm_started_rule = MatchRule::new_signal("org.chromium.VmConcierge", "VmStartedSignal");
if conn
.add_match_no_cb(&vm_started_rule.match_str())
.await
.is_err()
{
warn!("Unable to set filtering of VmStarted dbus message.")
}
conn.start_receive(
vm_started_rule,
Box::new(|msg, _| {
if let Ok(cid) =
crate::vm_grpc::vm_grpc_util::get_borealis_cid_from_vm_started_signal(&msg)
{
if let Err(e) = vm_grpc_init(cid) {
warn!("Failed to initialize GRPC client/server pair. {}", e);
}
}
true
}),
);
if conn
.add_match_no_cb(&vm_stopped_rule.match_str())
.await
.is_err()
{
warn!("Unable to set filtering of VmStarted dbus message.")
}
conn.start_receive(
vm_stopped_rule,
Box::new(|msg, _| {
if vm_stopped_signal_is_for_borealis(&msg).unwrap_or(false) {
info!("Got vm_stopped signal.");
if let Err(e) = request_vm_server_stop() {
warn!("Could not request grpc server stop. {:?}", e);
}
}
true
}),
);
Ok(())
}
/// Checks for borealis instance on-demand.
///
/// This functions checks to see if there is an existing borealis instance running. If there is,
/// it will start a new server/client pair and send a an init packet. 2 dbus calls have a max
/// timeout of 2 seconds.
///
/// `conn`: pre-initialized non-blocking dbus connection object.
///
/// # Return: Emtpty Result object.
pub async fn handle_startup_borealis_state_async(conn: Arc<SyncConnection>) -> Result<()> {
if let Ok(borealis_cid) = get_borealis_cid_from_concierge_async(conn).await {
info!("Borealis VM is already running. CID: {}", borealis_cid);
vm_grpc_init(borealis_cid)?;
} else {
info!("No borealis instance detected at resourced startup");
}
Ok(())
}