diff --git a/tools/agent-ctl/src/client.rs b/tools/agent-ctl/src/client.rs index b8dc231286..47e6a85bd7 100644 --- a/tools/agent-ctl/src/client.rs +++ b/tools/agent-ctl/src/client.rs @@ -8,7 +8,7 @@ use crate::types::{Config, Options}; use crate::utils; use anyhow::{anyhow, Result}; -use nix::sys::socket::{connect, socket, AddressFamily, SockAddr, SockFlag, SockType}; +use nix::sys::socket::{connect, socket, AddressFamily, SockAddr, SockFlag, SockType, UnixAddr}; use protocols::agent::*; use protocols::agent_ttrpc::*; use protocols::health::*; @@ -16,7 +16,8 @@ use protocols::health_ttrpc::*; use slog::{debug, info}; use std::io; use std::io::Write; // XXX: for flush() -use std::os::unix::io::RawFd; +use std::os::unix::io::{IntoRawFd, RawFd}; +use std::os::unix::net::UnixStream; use std::thread::sleep; use std::time::Duration; use ttrpc; @@ -294,25 +295,123 @@ fn client_create_vsock_fd(cid: libc::c_uint, port: u32) -> Result { Ok(fd) } -fn create_ttrpc_client(cid: libc::c_uint, port: u32) -> Result { - let fd = client_create_vsock_fd(cid, port).map_err(|e| { - anyhow!(format!( - "failed to create VSOCK connection (check agent is running): {:?}", - e - )) - })?; +fn create_ttrpc_client(server_address: String) -> Result { + if server_address == "" { + return Err(anyhow!("server address cannot be blank")); + } + + let fields: Vec<&str> = server_address.split("://").collect(); + + if fields.len() != 2 { + return Err(anyhow!("invalid server address URI")); + } + + let scheme = fields[0].to_lowercase(); + + let fd: RawFd = match scheme.as_str() { + // Formats: + // + // - "unix://absolute-path" (domain socket) + // (example: "unix:///tmp/domain.socket") + // + // - "unix://@absolute-path" (abstract socket) + // (example: "unix://@/tmp/abstract.socket") + // + "unix" => { + let mut abstract_socket = false; + + let mut path = fields[1].to_string(); + + if path.starts_with('@') { + abstract_socket = true; + + // Remove the magic abstract-socket request character ('@') + // and crucially add a trailing nul terminator (required to + // interoperate with the ttrpc crate). + path = path[1..].to_string() + &"\x00".to_string(); + } + + if abstract_socket { + let socket_fd = match socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) { + Ok(s) => s, + Err(e) => return Err(anyhow!(e).context("Failed to create Unix Domain socket")), + }; + + let unix_addr = match UnixAddr::new_abstract(path.as_bytes()) { + Ok(s) => s, + Err(e) => { + return Err( + anyhow!(e).context("Failed to create Unix Domain abstract socket") + ) + } + }; + + let sock_addr = SockAddr::Unix(unix_addr); + + connect(socket_fd, &sock_addr).map_err(|e| { + anyhow!(e).context("Failed to connect to Unix Domain abstract socket") + })?; + + socket_fd + } else { + let stream = match UnixStream::connect(path) { + Ok(s) => s, + Err(e) => { + return Err( + anyhow!(e).context("failed to create named UNIX Domain stream socket") + ) + } + }; + + stream.into_raw_fd() + } + } + // Format: "vsock://cid:port" + "vsock" => { + let addr: Vec<&str> = fields[1].split(':').collect(); + + if addr.len() != 2 { + return Err(anyhow!("invalid VSOCK server address URI")); + } + + let cid: u32 = match addr[0] { + "-1" | "" => libc::VMADDR_CID_ANY, + _ => match addr[0].parse::() { + Ok(c) => c, + Err(e) => return Err(anyhow!(e).context("VSOCK CID is not numeric")), + }, + }; + + let port: u32 = match addr[1].parse::() { + Ok(r) => r, + Err(e) => return Err(anyhow!(e).context("VSOCK port is not numeric")), + }; + + client_create_vsock_fd(cid, port).map_err(|e| { + anyhow!(e).context("failed to create VSOCK connection (check agent is running)") + })? + } + _ => { + return Err(anyhow!("invalid server address URI scheme: {:?}", scheme)); + } + }; Ok(ttrpc::client::Client::new(fd)) } -fn kata_service_agent(cid: libc::c_uint, port: u32) -> Result { - let ttrpc_client = create_ttrpc_client(cid, port)?; +fn kata_service_agent(server_address: String) -> Result { + let ttrpc_client = create_ttrpc_client(server_address)?; Ok(AgentServiceClient::new(ttrpc_client)) } -fn kata_service_health(cid: libc::c_uint, port: u32) -> Result { - let ttrpc_client = create_ttrpc_client(cid, port)?; +fn kata_service_health(server_address: String) -> Result { + let ttrpc_client = create_ttrpc_client(server_address)?; Ok(HealthClient::new(ttrpc_client)) } @@ -344,15 +443,10 @@ pub fn client(cfg: &Config, commands: Vec<&str>) -> Result<()> { announce(cfg); - let cid = cfg.cid; - let port = cfg.port; - - let addr = format!("vsock://{}:{}", cid, port); - // Create separate connections for each of the services provided // by the agent. - let client = kata_service_agent(cid, port as u32)?; - let health = kata_service_health(cid, port as u32)?; + let client = kata_service_agent(cfg.server_address.clone())?; + let health = kata_service_health(cfg.server_address.clone())?; let mut options = Options::new(); @@ -365,7 +459,7 @@ pub fn client(cfg: &Config, commands: Vec<&str>) -> Result<()> { options.insert("bundle-dir".to_string(), cfg.bundle_dir.clone()); info!(sl!(), "client setup complete"; - "server-address" => addr); + "server-address" => cfg.server_address.to_string()); if cfg.interactive { return interactive_client_loop(&cfg, &mut options, &client, &health); @@ -533,8 +627,8 @@ fn interactive_client_loop( let mut repeat_count: i64 = 1; loop { - let cmdline = readline("Enter command") - .map_err(|e| anyhow!(format!("failed to read line: {}", e)))?; + let cmdline = + readline("Enter command").map_err(|e| anyhow!(e).context("failed to read line"))?; if cmdline == "" { continue; @@ -592,7 +686,7 @@ fn agent_cmd_health_check( let reply = health .check(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -615,7 +709,7 @@ fn agent_cmd_health_version( let reply = health .version(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -637,7 +731,7 @@ fn agent_cmd_sandbox_create( let reply = client .create_sandbox(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -656,7 +750,7 @@ fn agent_cmd_sandbox_destroy( let reply = client .destroy_sandbox(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -686,7 +780,7 @@ fn agent_cmd_container_create( let reply = client .create_container(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -709,7 +803,7 @@ fn agent_cmd_container_remove( let reply = client .remove_container(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -746,7 +840,7 @@ fn agent_cmd_container_exec( let reply = client .exec_process(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -769,7 +863,7 @@ fn agent_cmd_container_stats( let reply = client .stats_container(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -792,7 +886,7 @@ fn agent_cmd_container_pause( let reply = client .pause_container(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -815,7 +909,7 @@ fn agent_cmd_container_resume( let reply = client .resume_container(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -838,7 +932,7 @@ fn agent_cmd_container_start( let reply = client .start_container(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -859,7 +953,7 @@ fn agent_cmd_sandbox_guest_details( let reply = client .get_guest_details(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -889,7 +983,7 @@ fn agent_cmd_container_list_processes( let reply = client .list_processes(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -914,7 +1008,7 @@ fn agent_cmd_container_wait_process( let reply = client .wait_process(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -949,7 +1043,7 @@ fn agent_cmd_container_signal_process( let reply = client .signal_process(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -968,7 +1062,7 @@ fn agent_cmd_sandbox_tracing_start( let reply = client .start_tracing(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -987,7 +1081,7 @@ fn agent_cmd_sandbox_tracing_stop( let reply = client .stop_tracing(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -1006,7 +1100,7 @@ fn agent_cmd_sandbox_update_interface( let reply = client .update_interface(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; // FIXME: Implement 'UpdateInterface' fully. eprintln!("FIXME: 'UpdateInterface' not fully implemented"); @@ -1031,7 +1125,7 @@ fn agent_cmd_sandbox_update_routes( let reply = client .update_routes(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; // FIXME: Implement 'UpdateRoutes' fully. eprintln!("FIXME: 'UpdateRoutes' not fully implemented"); @@ -1056,7 +1150,7 @@ fn agent_cmd_sandbox_list_interfaces( let reply = client .list_interfaces(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -1075,7 +1169,7 @@ fn agent_cmd_sandbox_list_routes( let reply = client .list_routes(&req, cfg.timeout_nano) - .map_err(|e| anyhow!(format!("{}: {:?}", ERR_API_FAILED, e)))?; + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; info!(sl!(), "response received"; "response" => format!("{:?}", reply)); diff --git a/tools/agent-ctl/src/main.rs b/tools/agent-ctl/src/main.rs index 2280f00873..c9514772a4 100644 --- a/tools/agent-ctl/src/main.rs +++ b/tools/agent-ctl/src/main.rs @@ -40,27 +40,36 @@ const WARNING_TEXT: &str = r#"WARNING: sandbox."#; fn make_examples_text(program_name: &str) -> String { + let abstract_server_address = "unix://@/foo/bar/abstract.socket"; let bundle = "$bundle_dir"; - let cid = 3; - let container_id = "$container_id"; let config_file_uri = "file:///tmp/config.json"; - let port = 1024; + let container_id = "$container_id"; + let local_server_address = "unix:///tmp/local.socket"; let sandbox_id = "$sandbox_id"; + let vsock_server_address = "vsock://3:1024"; format!( r#"EXAMPLES: - Check if the agent is running: - $ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd Check + $ {program} connect --server-address "{vsock_server_address}" --cmd Check + +- Connect to the agent using local sockets (when running in same environment as the agent): + + # Local socket + $ {program} connect --server-address "{local_server_address}" --cmd Check + + # Abstract socket + $ {program} connect --server-address "{abstract_server_address}" --cmd Check - Query the agent environment: - $ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd GuestDetails + $ {program} connect --server-address "{vsock_server_address}" --cmd GuestDetails - List all available (built-in and Kata Agent API) commands: - $ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd list + $ {program} connect --server-address "{vsock_server_address}" --cmd list - Generate a random container ID: @@ -72,33 +81,34 @@ fn make_examples_text(program_name: &str) -> String { - Attempt to create 7 sandboxes, ignoring any errors: - $ {program} connect --vsock-cid {cid} --vsock-port {port} --repeat 7 --cmd CreateSandbox + $ {program} connect --server-address "{vsock_server_address}" --repeat 7 --cmd CreateSandbox - Query guest details forever: - $ {program} connect --vsock-cid {cid} --vsock-port {port} --repeat -1 --cmd GuestDetails + $ {program} connect --server-address "{vsock_server_address}" --repeat -1 --cmd GuestDetails - Send a 'SIGUSR1' signal to a container process: - $ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd 'SignalProcess signal=usr1 sid={sandbox_id} cid={container_id}' + $ {program} connect --server-address "{vsock_server_address}" --cmd 'SignalProcess signal=usr1 sid={sandbox_id} cid={container_id}' - Create a sandbox with a single container, and then destroy everything: - $ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd CreateSandbox - $ {program} connect --vsock-cid {cid} --vsock-port {port} --bundle-dir {bundle:?} --cmd CreateContainer - $ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd DestroySandbox + $ {program} connect --server-address "{vsock_server_address}" --cmd CreateSandbox + $ {program} connect --server-address "{vsock_server_address}" --bundle-dir {bundle:?} --cmd CreateContainer + $ {program} connect --server-address "{vsock_server_address}" --cmd DestroySandbox - Create a Container using a custom configuration file: - $ {program} connect --vsock-cid {cid} --vsock-port {port} --bundle-dir {bundle:?} --cmd 'CreateContainer spec={config_file_uri}' + $ {program} connect --server-address "{vsock_server_address}" --bundle-dir {bundle:?} --cmd 'CreateContainer spec={config_file_uri}' "#, + abstract_server_address = abstract_server_address, bundle = bundle, - cid = cid, config_file_uri = config_file_uri, container_id = container_id, - port = port, + local_server_address = local_server_address, program = program_name, sandbox_id = sandbox_id, + vsock_server_address = vsock_server_address, ) } @@ -111,24 +121,11 @@ fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> { let interactive = args.is_present("interactive"); let ignore_errors = args.is_present("ignore-errors"); - let cid_str = args - .value_of("vsock-cid") - .ok_or("need VSOCK cid".to_string()) + let server_address = args + .value_of("server-address") + .ok_or("need server adddress".to_string()) .map_err(|e| anyhow!(e))?; - let port_str = args - .value_of("vsock-port") - .ok_or("need VSOCK port number".to_string()) - .map_err(|e| anyhow!(e))?; - - let cid: u32 = cid_str - .parse::() - .map_err(|e| anyhow!(format!("invalid VSOCK CID number: {}", e.to_string())))?; - - let port: u32 = port_str - .parse::() - .map_err(|e| anyhow!(format!("invalid VSOCK port number: {}", e)))?; - let mut commands: Vec<&str> = Vec::new(); if !interactive { @@ -156,8 +153,7 @@ fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> { let result = rpc::run( &logger, - cid, - port, + server_address, bundle_dir, interactive, ignore_errors, @@ -200,13 +196,6 @@ fn real_main() -> Result<()> { .takes_value(true) .value_name("directory"), ) - .arg( - Arg::with_name("vsock-cid") - .long("vsock-cid") - .help("VSOCK Context ID") - .takes_value(true) - .value_name("CID"), - ) .arg( Arg::with_name("cmd") .long("cmd") @@ -227,11 +216,11 @@ fn real_main() -> Result<()> { .help("Allow interactive client"), ) .arg( - Arg::with_name("vsock-port") - .long("vsock-port") - .help("VSOCK Port number") + Arg::with_name("server-address") + .long("server-address") + .help("server URI (vsock:// or unix://)") .takes_value(true) - .value_name("port-number"), + .value_name("URI"), ) .arg( Arg::with_name("timeout") diff --git a/tools/agent-ctl/src/rpc.rs b/tools/agent-ctl/src/rpc.rs index 3a4cd5a098..988fe43d4a 100644 --- a/tools/agent-ctl/src/rpc.rs +++ b/tools/agent-ctl/src/rpc.rs @@ -13,8 +13,7 @@ use crate::types::Config; pub fn run( logger: &Logger, - cid: u32, - port: u32, + server_address: &str, bundle_dir: &str, interactive: bool, ignore_errors: bool, @@ -22,8 +21,7 @@ pub fn run( commands: Vec<&str>, ) -> Result<()> { let cfg = Config { - cid: cid, - port: port, + server_address: server_address.to_string(), bundle_dir: bundle_dir.to_string(), timeout_nano: timeout_nano, interactive: interactive, diff --git a/tools/agent-ctl/src/types.rs b/tools/agent-ctl/src/types.rs index b9992d70e5..65ccfcb9a8 100644 --- a/tools/agent-ctl/src/types.rs +++ b/tools/agent-ctl/src/types.rs @@ -11,8 +11,7 @@ pub type Options = HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { - pub cid: u32, - pub port: u32, + pub server_address: String, pub bundle_dir: String, pub timeout_nano: i64, pub interactive: bool, diff --git a/tools/agent-ctl/src/utils.rs b/tools/agent-ctl/src/utils.rs index 3c5274e42a..6864847605 100644 --- a/tools/agent-ctl/src/utils.rs +++ b/tools/agent-ctl/src/utils.rs @@ -250,7 +250,7 @@ fn config_file_from_bundle_dir(bundle_dir: &str) -> Result { config_path .into_os_string() .into_string() - .map_err(|e| anyhow!(format!("failed to construct config file path: {:?}", e))) + .map_err(|e| anyhow!("{:?}", e).context("failed to construct config file path")) } fn root_oci_to_grpc(bundle_dir: &str, root: &ociRoot) -> Result { @@ -265,7 +265,7 @@ fn root_oci_to_grpc(bundle_dir: &str, root: &ociRoot) -> Result { abs_root_dir .into_os_string() .into_string() - .map_err(|e| anyhow!(format!("failed to construct bundle path: {:?}", e)))? + .map_err(|e| anyhow!("{:?}", e).context("failed to construct bundle path"))? }; let grpc_root = grpcRoot {