#![deny(missing_docs)]
#![deny(warnings)]
use libc::{c_void, close};
use nix::poll::poll;
use nix::poll::{PollFd, PollFlags};
use nix::sys::socket::{connect, socket};
use nix::sys::socket::{AddressFamily, SockFlag, SockType, VsockAddr};
use nix::sys::time::{TimeVal, TimeValLike};
use nix::unistd::read;
use std::io::Write;
use std::mem::size_of;
use std::os::unix::io::IntoRawFd;
use std::os::unix::io::RawFd;
use std::thread::sleep;
use std::time::{Duration, SystemTime};
use vmm_sys_util::epoll::{ControlOperation, Epoll, EpollEvent, EventSet};
use vmm_sys_util::timerfd::TimerFd;
use crate::common::{NitroCliErrorEnum, NitroCliFailure, NitroCliResult};
use crate::new_nitro_cli_failure;
const BUFFER_SIZE: usize = 1024;
const CONSOLE_CONNECT_TIMEOUT: i64 = 20000;
const POLL_TIMEOUT: i32 = 10000;
const SO_VM_SOCKETS_CONNECT_TIMEOUT: i32 = 6;
const TIMEOUT: u64 = 100;
pub enum PcrType {
DefaultType,
SigningCertificate,
}
pub struct Console {
fd: RawFd,
}
impl Drop for Console {
fn drop(&mut self) {
unsafe { close(self.fd) };
}
}
impl Console {
pub fn new(cid: u32, port: u32) -> NitroCliResult<Self> {
let socket_fd = socket(
AddressFamily::Vsock,
SockType::Stream,
SockFlag::empty(),
None,
)
.map_err(|err| {
new_nitro_cli_failure!(
&format!("Failed to create blocking console socket: {err:?}"),
NitroCliErrorEnum::SocketError
)
})?;
let sockaddr = VsockAddr::new(cid, port);
vsock_set_connect_timeout(socket_fd, CONSOLE_CONNECT_TIMEOUT).map_err(|err| {
err.add_subaction("Failed to set console connect timeout".to_string())
})?;
connect(socket_fd, &sockaddr).map_err(|err| {
new_nitro_cli_failure!(
&format!("Failed to connect to the console: {err:?}"),
NitroCliErrorEnum::EnclaveConsoleConnectionFailure
)
})?;
Ok(Console { fd: socket_fd })
}
pub fn new_nonblocking(cid: u32, port: u32) -> NitroCliResult<Self> {
let socket_fd = socket(
AddressFamily::Vsock,
SockType::Stream,
SockFlag::SOCK_NONBLOCK,
None,
)
.map_err(|err| {
new_nitro_cli_failure!(
&format!("Failed to create nonblocking console socket: {err:?}"),
NitroCliErrorEnum::SocketError
)
})?;
vsock_set_connect_timeout(socket_fd, CONSOLE_CONNECT_TIMEOUT).map_err(|err| {
err.add_subaction("Failed to set console connect timeout".to_string())
})?;
let sockaddr = VsockAddr::new(cid, port);
let result = connect(socket_fd, &sockaddr);
match result {
Ok(_) => println!("Connected to the console"),
Err(error) => match error {
nix::errno::Errno::EINPROGRESS => {
let poll_fd = PollFd::new(socket_fd, PollFlags::POLLOUT);
let mut poll_fds = [poll_fd];
match poll(&mut poll_fds, POLL_TIMEOUT) {
Ok(1) => println!("Connected to the console"),
_ => {
return Err(new_nitro_cli_failure!(
"Failed to connect to the console",
NitroCliErrorEnum::SocketError
))
}
}
}
_ => {
return Err(new_nitro_cli_failure!(
"Failed to connect to the console",
NitroCliErrorEnum::SocketError
))
}
},
};
Ok(Console { fd: socket_fd })
}
pub fn read_to(
&self,
output: &mut dyn Write,
disconnect_timeout_sec: Option<u64>,
) -> NitroCliResult<()> {
let epoll = Epoll::new().map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to create epoll: {e:?}"),
NitroCliErrorEnum::EpollError
)
})?;
epoll
.ctl(
ControlOperation::Add,
self.fd,
EpollEvent::new(EventSet::IN, self.fd as u64),
)
.map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to add fd to epoll: {e:?}"),
NitroCliErrorEnum::EpollError
)
})?;
if let Some(disconnect_timeout) = disconnect_timeout_sec {
let mut timerfd = TimerFd::new().map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to initialize timerfd: {e:?}"),
NitroCliErrorEnum::EpollError
)
})?;
timerfd
.reset(Duration::from_secs(disconnect_timeout), None)
.map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to arm timerfd: {e:?}"),
NitroCliErrorEnum::EpollError
)
})?;
let timerfd_fd = timerfd.into_raw_fd();
epoll
.ctl(
ControlOperation::Add,
timerfd_fd,
EpollEvent::new(EventSet::IN, timerfd_fd as u64),
)
.map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to add fd to epoll: {e:?}"),
NitroCliErrorEnum::EpollError
)
})?;
}
let mut events = [EpollEvent::default(); 1];
loop {
let num_events = epoll.wait(-1, &mut events).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to wait epoll: {e:?}"),
NitroCliErrorEnum::EpollError
)
})?;
if num_events == 1 {
match events[0].fd() {
fd if fd == self.fd => {
let mut buffer = [0u8; BUFFER_SIZE];
let size = read(self.fd, &mut buffer).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to read data from the console: {e:?}"),
NitroCliErrorEnum::EnclaveConsoleReadError
)
})?;
if size == 0 {
break;
}
if size > 0 {
output.write(&buffer[..size]).map_err(|e| {
new_nitro_cli_failure!(
&format!(
"Failed to write data from the \
console to the given stream: {e:?}"
),
NitroCliErrorEnum::EnclaveConsoleWriteOutputError
)
})?;
}
}
_ => break,
}
}
}
Ok(())
}
pub fn read_to_buffer(&self, buf: &mut Vec<u8>, duration: Duration) -> NitroCliResult<()> {
let sys_time = SystemTime::now();
loop {
let mut buffer = [0u8; BUFFER_SIZE];
let result = read(self.fd, &mut buffer);
if let Ok(size) = result {
if size > 0 {
let mut buf_vec = buffer.to_vec();
buf_vec.truncate(size);
(*buf).append(&mut buf_vec);
}
}
sleep(Duration::from_millis(TIMEOUT));
let time_elapsed = sys_time.elapsed().map_err(|err| {
new_nitro_cli_failure!(
&format!("System time moved backwards: {err:?}"),
NitroCliErrorEnum::ClockSkewError
)
})?;
if time_elapsed >= duration {
break;
}
}
Ok(())
}
}
fn vsock_set_connect_timeout(fd: RawFd, millis: i64) -> NitroCliResult<()> {
let timeval = TimeVal::milliseconds(millis);
let ret = unsafe {
libc::setsockopt(
fd,
libc::AF_VSOCK,
SO_VM_SOCKETS_CONNECT_TIMEOUT,
&timeval as *const _ as *const c_void,
size_of::<TimeVal>() as u32,
)
};
match ret {
0 => Ok(()),
_ => Err(new_nitro_cli_failure!(
&format!("Failed to configure SO_VM_SOCKETS_CONNECT_TIMEOUT: {ret:?}"),
NitroCliErrorEnum::SocketConnectTimeoutError
)),
}
}
pub fn ceil_div(lhs: u64, rhs: u64) -> u64 {
if rhs == 0 {
return u64::MAX;
}
lhs / rhs
+ match lhs % rhs {
0 => 0,
_ => 1,
}
}