1use std::collections::HashMap;
2use std::io::{BufReader, BufWriter, Read, Write};
3use std::net::{Shutdown, TcpListener, TcpStream};
4use std::os::fd::AsRawFd;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Mutex};
7use std::time::Duration;
8
9use anyhow::{bail, Context, Result};
10use crossbeam_channel::Receiver;
11
12use crate::backend::network::FileHandleNetworkAttachment;
13use crate::backend::terminal;
14use crate::backend::*;
15
16use vm_proto::{
17 frame, ChmodRequest, CopyRequest, DiscardRequest, ExecRequest, ForwardRequest, ForwardResponse,
18 FsOkResponse, MkdirRequest, MountRequest, MountResponse, PortMapping, ReadDirRequest,
19 ReadDirResponse, ReadFileRequest, RemoveRequest, RenameRequest, StatRequest, StatResponse,
20 WatchRequest, WriteFileRequest, WriteFileResponse, VSOCK_PORT, VSOCK_PORT_FORWARD,
21};
22
23pub fn command_line(verbose: bool, console: bool) -> String {
36 let base = "root=/dev/vda rw init=/usr/bin/vm-guest mitigations=off printk.time=1";
37 if verbose {
38 format!("console={} {}", CONSOLE_DEVICE, base)
39 } else if console {
40 format!("console={} {} quiet", CONSOLE_DEVICE, base)
41 } else {
42 format!("{} quiet", base)
43 }
44}
45
46#[derive(Debug, Clone)]
49pub struct MountConfig {
50 pub host_path: String,
51 pub guest_path: String,
52 pub read_only: bool,
53}
54
55pub struct VmConfigBuilder {
58 kernel: Option<String>,
59 rootfs: Option<String>,
60 initrd: Option<String>,
61 cpus: usize,
62 memory_mb: u64,
63 console: bool,
64 verbose: bool,
65 network_fd: Option<i32>,
66 nbd_uri: Option<String>,
67 mounts: Vec<MountConfig>,
68 sync_disk: bool,
69}
70
71impl VmConfigBuilder {
72 pub(crate) fn new() -> Self {
73 VmConfigBuilder {
74 kernel: None,
75 rootfs: None,
76 initrd: None,
77 cpus: 2,
78 memory_mb: 2048,
79 console: true,
80 verbose: false,
81 network_fd: None,
82 nbd_uri: None,
83 mounts: Vec::new(),
84 sync_disk: true,
85 }
86 }
87
88 pub fn sync_disk(mut self, enabled: bool) -> Self {
93 self.sync_disk = enabled;
94 self
95 }
96
97 pub fn console(mut self, enabled: bool) -> Self {
101 self.console = enabled;
102 self
103 }
104
105 pub fn verbose(mut self, enabled: bool) -> Self {
108 self.verbose = enabled;
109 self
110 }
111
112 pub fn kernel(mut self, path: impl Into<String>) -> Self {
113 self.kernel = Some(path.into());
114 self
115 }
116
117 pub fn rootfs(mut self, path: impl Into<String>) -> Self {
118 self.rootfs = Some(path.into());
119 self
120 }
121
122 pub fn initrd(mut self, path: impl Into<String>) -> Self {
123 self.initrd = Some(path.into());
124 self
125 }
126
127 pub fn cpus(mut self, n: usize) -> Self {
128 self.cpus = n;
129 self
130 }
131
132 pub fn memory_mb(mut self, mb: u64) -> Self {
133 self.memory_mb = mb;
134 self
135 }
136
137 pub fn network_fd(mut self, fd: i32) -> Self {
139 self.network_fd = Some(fd);
140 self
141 }
142
143 pub fn nbd_uri(mut self, uri: impl Into<String>) -> Self {
145 self.nbd_uri = Some(uri.into());
146 self
147 }
148
149 pub fn mount(mut self, config: MountConfig) -> Self {
151 self.mounts.push(config);
152 self
153 }
154
155 pub fn build(self) -> Result<Sandbox> {
156 let kernel_path = self.kernel.context("kernel path is required")?;
157 let rootfs_path = self.rootfs.context("rootfs path is required")?;
158
159 if !VirtualMachine::supported() {
160 bail!("Virtualization is not supported on this machine");
161 }
162
163 let boot_loader = LinuxBootLoader::new_with_kernel(&kernel_path);
164 if let Some(ref initrd) = self.initrd {
165 boot_loader.set_initrd(initrd);
166 }
167
168 boot_loader.set_command_line(&command_line(self.verbose, self.console));
169
170 let memory_bytes = self.memory_mb * 1024 * 1024;
171 let config = VirtualMachineConfiguration::new(&boot_loader, self.cpus, memory_bytes);
172
173 if self.console || self.verbose {
176 let serial_attachment = if self.console {
177 FileHandleSerialAttachment::new(
178 std::io::stdin().as_raw_fd(),
179 std::io::stdout().as_raw_fd(),
180 )
181 } else {
182 FileHandleSerialAttachment::new_write_only(std::io::stderr().as_raw_fd())
183 };
184 let serial = VirtioConsoleSerialPort::new_with_attachment(&serial_attachment);
185 config.set_serial_ports(&[serial]);
186 }
187
188 let nbd_attachment;
189 let disk_attachment;
190 let block_device = if let Some(ref uri) = self.nbd_uri {
191 nbd_attachment = NbdAttachment::new(uri, 30.0, false)
192 .map_err(|e| anyhow::anyhow!("Failed to create NBD attachment: {}", e))?;
193 VirtioBlockDevice::new(&nbd_attachment)
194 } else {
195 let sync_mode = if self.sync_disk {
196 DiskImageSynchronizationMode::Fsync
197 } else {
198 DiskImageSynchronizationMode::None
199 };
200 disk_attachment = DiskImageAttachment::new_with_options(
201 &rootfs_path,
202 false,
203 DiskImageCachingMode::Cached,
204 sync_mode,
205 )
206 .map_err(|e| anyhow::anyhow!("Failed to create disk attachment: {}", e))?;
207 VirtioBlockDevice::new(&disk_attachment)
208 };
209 config.set_storage_devices(&[&block_device]);
210
211 if let Some(fd) = self.network_fd {
212 let net_attachment = FileHandleNetworkAttachment::new(fd);
213 let net_device = VirtioNetworkDevice::new_with_attachment(&net_attachment);
214 net_device.set_mac_address(&MACAddress::random_local());
215 config.set_network_devices(&[net_device]);
216 }
217
218 let mut fs_devices: Vec<VirtioFileSystemDevice> = Vec::new();
220 let mut mount_requests: Vec<MountRequest> = Vec::new();
221
222 for (i, m) in self.mounts.iter().enumerate() {
223 let tag = format!("mount{}", i);
224 let shared_dir = SharedDirectory::new(&m.host_path, m.read_only);
225 fs_devices.push(VirtioFileSystemDevice::new(&tag, &shared_dir));
226 mount_requests.push(MountRequest {
227 tag,
228 guest_path: m.guest_path.clone(),
229 read_only: m.read_only,
230 });
231 }
232
233 if !fs_devices.is_empty() {
234 config.set_directory_sharing_devices(&fs_devices);
235 }
236
237 let socket_device = VirtioSocketDevice::new();
238 config.set_socket_devices(&[socket_device]);
239
240 config.set_entropy_devices(&[VirtioEntropyDevice::new()]);
241
242 config
243 .validate()
244 .map_err(|e| anyhow::anyhow!("VM configuration invalid: {}", e))?;
245
246 Ok(Sandbox {
247 vm: Arc::new(VirtualMachine::new(&config)),
248 mounts: Mutex::new(mount_requests),
249 })
250 }
251}
252
253pub struct Sandbox {
256 vm: Arc<VirtualMachine>,
257 mounts: Mutex<Vec<MountRequest>>,
258}
259
260impl Sandbox {
261 pub fn builder() -> VmConfigBuilder {
262 VmConfigBuilder::new()
263 }
264
265 pub fn start(&self) -> Result<()> {
266 self.vm
267 .start()
268 .map_err(|e| anyhow::anyhow!("Failed to start VM: {}", e))
269 }
270
271 pub fn stop(&self) -> Result<()> {
272 self.vm
273 .stop()
274 .map_err(|e| anyhow::anyhow!("Failed to stop VM: {}", e))
275 }
276
277 pub fn wait_ready(&self) -> Result<()> {
285 self.connect_vsock().map(drop)
286 }
287
288 pub fn state_channel(&self) -> Receiver<VmState> {
289 self.vm.state_channel()
290 }
291
292 fn send_mount_requests(&self, writer: &mut impl Write, reader: &mut impl Read) -> Result<()> {
295 let mounts = std::mem::take(&mut *self.mounts.lock().unwrap());
296 for req in &mounts {
297 frame::send_json(writer, frame::MOUNT_REQ, &req).context("sending mount request")?;
298 let (_msg_type, payload) = frame::read_frame(reader)
299 .context("reading mount response")?
300 .context("guest closed connection during mount init")?;
301 let resp: MountResponse = match serde_json::from_slice(&payload) {
302 Ok(r) => r,
303 Err(_) => {
304 bail!(
305 "guest does not support directory mounts. \
306 Run `hanzo-vm upgrade` and recreate the checkpoint to enable --mount."
307 );
308 }
309 };
310 if !resp.ok {
311 bail!(
312 "mount failed: {} -> {}: {}",
313 req.tag,
314 req.guest_path,
315 resp.error.unwrap_or_else(|| "unknown error".into())
316 );
317 }
318 }
319 Ok(())
320 }
321
322 pub fn exec(
325 &self,
326 argv: &[impl AsRef<str>],
327 stdout: &mut impl Write,
328 stderr: &mut impl Write,
329 ) -> Result<i32> {
330 self.exec_with_env(argv, &HashMap::new(), stdout, stderr)
331 }
332
333 pub fn exec_with_env(
334 &self,
335 argv: &[impl AsRef<str>],
336 env: &HashMap<String, String>,
337 stdout: &mut impl Write,
338 stderr: &mut impl Write,
339 ) -> Result<i32> {
340 let stream = self.connect_vsock()?;
341 let mut writer = stream.try_clone()?;
342 let mut reader = stream;
343
344 self.send_mount_requests(&mut writer, &mut reader)?;
345
346 let req = ExecRequest {
347 argv: argv.iter().map(|s| s.as_ref().to_string()).collect(),
348 env: env.clone(),
349 tty: None,
350 rows: None,
351 cols: None,
352 cwd: None,
353 };
354 frame::send_json(&mut writer, frame::EXEC_REQ, &req)?;
355
356 let mut exit_code = 0;
357
358 loop {
359 match frame::read_frame(&mut reader).context("reading vsock response")? {
360 Some((frame::STDOUT, payload)) => {
361 stdout.write_all(&payload)?;
362 }
363 Some((frame::STDERR, payload)) => {
364 stderr.write_all(&payload)?;
365 }
366 Some((frame::EXIT, payload)) => {
367 exit_code = frame::parse_exit_code(&payload).unwrap_or(0);
368 break;
369 }
370 Some((frame::ERROR, payload)) => {
371 let msg = String::from_utf8_lossy(&payload);
372 write!(stderr, "guest error: {}", msg)?;
373 exit_code = 1;
374 break;
375 }
376 Some(_) => {} None => break, }
379 }
380
381 Ok(exit_code)
382 }
383
384 pub fn attest(&self, bind: &[u8; 64]) -> Result<vm_measure::attest::Status> {
395 let stream = self.connect_vsock()?;
396 let mut writer = stream.try_clone()?;
397 let mut reader = stream;
398 reader.set_read_timeout(Some(Duration::from_secs(5)))?;
399
400 self.send_mount_requests(&mut writer, &mut reader)?;
401
402 frame::write_frame(&mut writer, frame::ATTEST_REQ, bind)?;
403 match frame::read_frame(&mut reader).context("reading attest response")? {
404 Some((frame::ATTEST_RESP, payload)) => {
405 serde_json::from_slice(&payload).context("reading the guest's attestation status")
406 }
407 Some((frame::ERROR, payload)) => bail!("{}", String::from_utf8_lossy(&payload)),
408 Some((other, _)) => bail!("unexpected frame type 0x{other:02x} in attest response"),
409 None => bail!("guest closed connection during attest"),
410 }
411 }
412
413 pub fn read_file(&self, path: &str) -> Result<Vec<u8>> {
414 let stream = self.connect_vsock()?;
415 let mut writer = stream.try_clone()?;
416 let mut reader = stream;
417
418 self.send_mount_requests(&mut writer, &mut reader)?;
419
420 let req = ReadFileRequest {
421 path: path.to_string(),
422 };
423 frame::send_json(&mut writer, frame::READ_FILE_REQ, &req)?;
424
425 match frame::read_frame(&mut reader).context("reading read_file response")? {
426 Some((frame::READ_FILE_RESP, payload)) => Ok(payload),
427 Some((frame::ERROR, payload)) => {
428 bail!("{}", String::from_utf8_lossy(&payload));
429 }
430 Some((other, _)) => {
431 bail!(
432 "unexpected frame type 0x{:02x} in read_file response",
433 other
434 );
435 }
436 None => bail!("guest closed connection during read_file"),
437 }
438 }
439
440 pub fn write_file(&self, path: &str, content: &[u8]) -> Result<()> {
441 let stream = self.connect_vsock()?;
442 let mut writer = stream.try_clone()?;
443 let mut reader = stream;
444
445 self.send_mount_requests(&mut writer, &mut reader)?;
446
447 let req = WriteFileRequest {
448 path: path.to_string(),
449 len: content.len() as u64,
450 };
451 frame::send_json(&mut writer, frame::WRITE_FILE_REQ, &req)?;
452 frame::write_frame(&mut writer, frame::WRITE_FILE_DATA, content)?;
453
454 let (_msg_type, payload) = frame::read_frame(&mut reader)
455 .context("reading write_file response")?
456 .context("guest closed connection during write_file")?;
457
458 let resp: WriteFileResponse =
459 serde_json::from_slice(&payload).context("parsing write_file response")?;
460
461 if !resp.ok {
462 bail!(
463 "write_file failed: {}",
464 resp.error.unwrap_or_else(|| "unknown error".into())
465 );
466 }
467
468 Ok(())
469 }
470
471 fn void_fs_op(&self, req_frame: u8, req: &impl serde::Serialize) -> Result<()> {
473 let stream = self.connect_vsock()?;
474 let mut writer = stream.try_clone()?;
475 let mut reader = stream;
476
477 self.send_mount_requests(&mut writer, &mut reader)?;
478
479 frame::send_json(&mut writer, req_frame, req)?;
480
481 match frame::read_frame(&mut reader).context("reading fs op response")? {
482 Some((frame::FS_OK_RESP, payload)) => {
483 let resp: FsOkResponse =
484 serde_json::from_slice(&payload).context("parsing fs ok response")?;
485 if !resp.ok {
486 bail!("{}", resp.error.unwrap_or_else(|| "unknown error".into()));
487 }
488 Ok(())
489 }
490 Some((frame::ERROR, payload)) => {
491 bail!("{}", String::from_utf8_lossy(&payload));
492 }
493 Some((other, _)) => {
494 bail!("unexpected frame type 0x{:02x}", other);
495 }
496 None => bail!("guest closed connection"),
497 }
498 }
499
500 pub fn mkdir(&self, path: &str, recursive: bool) -> Result<()> {
501 self.void_fs_op(
502 frame::MKDIR_REQ,
503 &MkdirRequest {
504 path: path.to_string(),
505 recursive,
506 },
507 )
508 }
509
510 pub fn download(
512 &self,
513 url: &str,
514 path: &str,
515 extract: bool,
516 strip_components: u32,
517 on_progress: impl Fn(vm_proto::DownloadProgress),
518 ) -> Result<()> {
519 let stream = self.connect_vsock()?;
520 let mut writer = stream.try_clone()?;
521 let mut reader = stream;
522
523 self.send_mount_requests(&mut writer, &mut reader)?;
524
525 let req = vm_proto::DownloadRequest {
526 url: url.to_string(),
527 path: path.to_string(),
528 extract,
529 strip_components,
530 };
531 frame::send_json(&mut writer, frame::DOWNLOAD_REQ, &req)?;
532
533 loop {
535 match frame::read_frame(&mut reader)? {
536 Some((frame::DOWNLOAD_PROGRESS, payload)) => {
537 if let Ok(progress) =
538 serde_json::from_slice::<vm_proto::DownloadProgress>(&payload)
539 {
540 on_progress(progress);
541 }
542 }
543 Some((frame::FS_OK_RESP, payload)) => {
544 let resp: FsOkResponse = serde_json::from_slice(&payload)?;
545 if !resp.ok {
546 bail!("{}", resp.error.unwrap_or_else(|| "download failed".into()));
547 }
548 return Ok(());
549 }
550 Some((frame::ERROR, payload)) => {
551 bail!("{}", String::from_utf8_lossy(&payload));
552 }
553 Some((other, _)) => {
554 bail!("unexpected frame 0x{:02x} during download", other);
555 }
556 None => bail!("connection closed during download"),
557 }
558 }
559 }
560
561 pub fn read_dir(&self, path: &str) -> Result<ReadDirResponse> {
562 let stream = self.connect_vsock()?;
563 let mut writer = stream.try_clone()?;
564 let mut reader = stream;
565
566 self.send_mount_requests(&mut writer, &mut reader)?;
567
568 let req = ReadDirRequest {
569 path: path.to_string(),
570 };
571 frame::send_json(&mut writer, frame::READ_DIR_REQ, &req)?;
572
573 match frame::read_frame(&mut reader).context("reading read_dir response")? {
574 Some((frame::READ_DIR_RESP, payload)) => {
575 Ok(serde_json::from_slice(&payload).context("parsing read_dir response")?)
576 }
577 Some((frame::ERROR, payload)) => {
578 bail!("{}", String::from_utf8_lossy(&payload));
579 }
580 Some((other, _)) => {
581 bail!("unexpected frame type 0x{:02x} in read_dir response", other);
582 }
583 None => bail!("guest closed connection during read_dir"),
584 }
585 }
586
587 pub fn stat(&self, path: &str) -> Result<StatResponse> {
588 let stream = self.connect_vsock()?;
589 let mut writer = stream.try_clone()?;
590 let mut reader = stream;
591
592 self.send_mount_requests(&mut writer, &mut reader)?;
593
594 let req = StatRequest {
595 path: path.to_string(),
596 };
597 frame::send_json(&mut writer, frame::STAT_REQ, &req)?;
598
599 match frame::read_frame(&mut reader).context("reading stat response")? {
600 Some((frame::STAT_RESP, payload)) => {
601 Ok(serde_json::from_slice(&payload).context("parsing stat response")?)
602 }
603 Some((frame::ERROR, payload)) => {
604 bail!("{}", String::from_utf8_lossy(&payload));
605 }
606 Some((other, _)) => {
607 bail!("unexpected frame type 0x{:02x} in stat response", other);
608 }
609 None => bail!("guest closed connection during stat"),
610 }
611 }
612
613 pub fn remove(&self, path: &str, recursive: bool) -> Result<()> {
614 self.void_fs_op(
615 frame::REMOVE_REQ,
616 &RemoveRequest {
617 path: path.to_string(),
618 recursive,
619 },
620 )
621 }
622
623 pub fn discard_overlay(&self, path: &str) -> Result<()> {
626 self.void_fs_op(
627 frame::DISCARD_REQ,
628 &DiscardRequest {
629 path: path.to_string(),
630 },
631 )
632 }
633
634 pub fn rename(&self, old_path: &str, new_path: &str) -> Result<()> {
635 self.void_fs_op(
636 frame::RENAME_REQ,
637 &RenameRequest {
638 old_path: old_path.to_string(),
639 new_path: new_path.to_string(),
640 },
641 )
642 }
643
644 pub fn copy(&self, src: &str, dst: &str, recursive: bool) -> Result<()> {
645 self.void_fs_op(
646 frame::COPY_REQ,
647 &CopyRequest {
648 src: src.to_string(),
649 dst: dst.to_string(),
650 recursive,
651 },
652 )
653 }
654
655 pub fn chmod(&self, path: &str, mode: u32) -> Result<()> {
656 self.void_fs_op(
657 frame::CHMOD_REQ,
658 &ChmodRequest {
659 path: path.to_string(),
660 mode,
661 },
662 )
663 }
664
665 pub fn open_exec(
669 &self,
670 argv: &[impl AsRef<str>],
671 env: &HashMap<String, String>,
672 cwd: Option<&str>,
673 ) -> Result<TcpStream> {
674 let stream = self.connect_vsock()?;
675 let mut writer = stream.try_clone()?;
676 let mut reader = stream.try_clone()?;
677
678 self.send_mount_requests(&mut writer, &mut reader)?;
679
680 let req = ExecRequest {
681 argv: argv.iter().map(|s| s.as_ref().to_string()).collect(),
682 env: env.clone(),
683 tty: None,
684 rows: None,
685 cols: None,
686 cwd: cwd.map(|s| s.to_string()),
687 };
688 frame::send_json(&mut writer, frame::EXEC_REQ, &req)?;
689
690 Ok(stream)
691 }
692
693 pub fn open_shell(
698 &self,
699 argv: &[impl AsRef<str>],
700 env: &HashMap<String, String>,
701 rows: u16,
702 cols: u16,
703 ) -> Result<TcpStream> {
704 self.open_shell_with_cwd(argv, env, rows, cols, None)
705 }
706
707 pub fn open_shell_with_cwd(
708 &self,
709 argv: &[impl AsRef<str>],
710 env: &HashMap<String, String>,
711 rows: u16,
712 cols: u16,
713 cwd: Option<&str>,
714 ) -> Result<TcpStream> {
715 let stream = self.connect_vsock()?;
716 let mut writer = stream.try_clone()?;
717 let mut reader = stream.try_clone()?;
718
719 self.send_mount_requests(&mut writer, &mut reader)?;
720
721 let req = ExecRequest {
722 argv: argv.iter().map(|s| s.as_ref().to_string()).collect(),
723 env: env.clone(),
724 tty: Some(true),
725 rows: Some(rows),
726 cols: Some(cols),
727 cwd: cwd.map(|s| s.to_string()),
728 };
729 frame::send_json(&mut writer, frame::EXEC_REQ, &req)?;
730
731 Ok(stream)
732 }
733
734 pub fn open_watch(&self, path: &str, recursive: bool) -> Result<TcpStream> {
737 let stream = self.connect_vsock()?;
738 let mut writer = stream.try_clone()?;
739 let mut reader = stream.try_clone()?;
740
741 self.send_mount_requests(&mut writer, &mut reader)?;
742
743 let req = WatchRequest {
744 path: path.to_string(),
745 recursive,
746 };
747 frame::send_json(&mut writer, frame::WATCH_REQ, &req)?;
748
749 Ok(stream)
750 }
751
752 pub fn shell(&self, argv: &[impl AsRef<str>], env: &HashMap<String, String>) -> Result<i32> {
757 let stdin_fd = std::io::stdin().as_raw_fd();
758 let (rows, cols) = terminal::terminal_size(stdin_fd);
759
760 let stream = self.connect_vsock()?;
761 let mut writer = stream.try_clone()?;
762 let mut reader = stream;
763
764 self.send_mount_requests(&mut writer, &mut reader)?;
766
767 let req = ExecRequest {
769 argv: argv.iter().map(|s| s.as_ref().to_string()).collect(),
770 env: env.clone(),
771 tty: Some(true),
772 rows: Some(rows),
773 cols: Some(cols),
774 cwd: None,
775 };
776 frame::send_json(&mut writer, frame::EXEC_REQ, &req)?;
777
778 let _raw_guard = terminal::TerminalState::enter_raw_mode(stdin_fd);
780
781 let (relay, shutdown_signal) =
783 terminal::StdinRelay::new(stdin_fd).expect("failed to init stdin relay");
784
785 let exit_code = Arc::new(Mutex::new(0i32));
786
787 let mut vsock_writer = writer.try_clone()?;
789 let stdin_thread = std::thread::spawn(move || {
790 let mut buf = [0u8; 4096];
791 loop {
792 match relay.wait() {
793 terminal::StdinEvent::Ready => {
794 let n = terminal::read_raw(stdin_fd, &mut buf);
795 if n == 0 {
796 break;
797 }
798 if frame::write_frame(&mut vsock_writer, frame::STDIN, &buf[..n]).is_err() {
799 break;
800 }
801 }
802 terminal::StdinEvent::Resize => {
803 let (rows, cols) = terminal::terminal_size(stdin_fd);
804 let payload = frame::resize_payload(rows, cols);
805 if frame::write_frame(&mut vsock_writer, frame::RESIZE, &payload).is_err() {
806 break;
807 }
808 }
809 terminal::StdinEvent::Shutdown => break,
810 }
811 }
812 });
813
814 let exit_code_b = exit_code.clone();
818 let vsock_thread = std::thread::spawn(move || {
819 let mut reader = BufReader::new(reader);
820 let mut stdout = BufWriter::new(std::io::stdout());
821 loop {
822 match frame::read_frame(&mut reader) {
823 Ok(Some((frame::STDOUT, payload))) => {
824 let _ = stdout.write_all(&payload);
825 if reader.buffer().is_empty() {
830 let _ = stdout.flush();
831 }
832 }
833 Ok(Some((frame::EXIT, payload))) => {
834 let _ = stdout.flush();
835 *exit_code_b.lock().unwrap() =
836 frame::parse_exit_code(&payload).unwrap_or(0);
837 break;
838 }
839 Ok(Some((frame::ERROR, payload))) => {
840 let _ = stdout.flush();
841 let msg = String::from_utf8_lossy(&payload);
842 let _ = std::io::stderr()
843 .write_all(format!("guest error: {}\r\n", msg).as_bytes());
844 *exit_code_b.lock().unwrap() = 1;
845 break;
846 }
847 Ok(Some(_)) => {} Ok(None) | Err(_) => break,
849 }
850 }
851 let _ = stdout.flush();
852 shutdown_signal.signal();
853 });
854
855 let _ = vsock_thread.join();
857 let _ = stdin_thread.join();
858
859 let code = *exit_code.lock().unwrap();
862 Ok(code)
863 }
864
865 pub fn start_port_forwarding(&self, forwards: &[PortMapping]) -> Result<PortForwardHandle> {
868 let stop = Arc::new(AtomicBool::new(false));
869 let mut listeners = Vec::new();
870
871 for mapping in forwards {
872 let addr: std::net::SocketAddr = format!("127.0.0.1:{}", mapping.host_port)
873 .parse()
874 .with_context(|| format!("Invalid port {}", mapping.host_port))?;
875 let socket = socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::STREAM, None)
876 .with_context(|| {
877 format!("Failed to create socket for port {}", mapping.host_port)
878 })?;
879 socket.set_reuse_address(true)?;
880 socket
881 .bind(&addr.into())
882 .with_context(|| format!("Failed to bind port {}", mapping.host_port))?;
883 socket
884 .listen(128)
885 .with_context(|| format!("Failed to listen on port {}", mapping.host_port))?;
886 socket.set_nonblocking(true)?;
887 let tcp_listener: TcpListener = socket.into();
888
889 let guest_port = mapping.guest_port;
890 let vm = Arc::clone(&self.vm);
891 let stop_flag = stop.clone();
892
893 eprintln!(
894 "hanzo-vm: forwarding 127.0.0.1:{} -> guest:{}",
895 mapping.host_port, mapping.guest_port
896 );
897
898 let handle = std::thread::spawn(move || {
899 while !stop_flag.load(Ordering::Relaxed) {
900 match tcp_listener.accept() {
901 Ok((tcp_stream, _)) => {
902 let _ = tcp_stream.set_nonblocking(false);
905 let vm = Arc::clone(&vm);
906 std::thread::spawn(move || {
907 if let Err(e) =
908 handle_forward_connection(tcp_stream, &vm, guest_port)
909 {
910 tracing::debug!("port forward error: {}", e);
911 }
912 });
913 }
914 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
915 std::thread::sleep(Duration::from_millis(50));
916 }
917 Err(e) => {
918 if !stop_flag.load(Ordering::Relaxed) {
919 tracing::debug!("accept error on port forward listener: {}", e);
920 }
921 break;
922 }
923 }
924 }
925 });
926
927 listeners.push(handle);
928 }
929
930 Ok(PortForwardHandle {
931 stop,
932 threads: listeners,
933 })
934 }
935
936 pub fn connect_forward(&self, guest_port: u16) -> Result<TcpStream> {
945 open_forward_stream(&self.vm, guest_port)
946 }
947
948 const REACHABLE: Duration = Duration::from_secs(60);
959
960 fn connect_vsock(&self) -> Result<TcpStream> {
961 let state_rx = self.vm.state_channel();
962 let start = std::time::Instant::now();
963 let mut last;
964 loop {
965 if let Ok(state) = state_rx.try_recv() {
967 match state {
968 VmState::Stopped => {
969 bail!("VM stopped during startup - check boot output above for errors")
970 }
971 VmState::Error => bail!("VM encountered an error during startup"),
972 _ => {}
973 }
974 }
975 match self.vm.connect_to_vsock_port(VSOCK_PORT) {
976 Ok(s) => {
977 let _ = s.set_nodelay(true);
978 return Ok(s);
979 }
980 Err(e) => last = e,
981 }
982 let waited = start.elapsed();
983 if waited >= Self::REACHABLE {
984 bail!(
985 "guest did not answer in {}s (last: {last})",
986 Self::REACHABLE.as_secs()
987 );
988 }
989 tracing::debug!("vsock connect failed after {waited:?}: {last}");
990 let interval = if waited < Duration::from_millis(100) {
993 1
994 } else {
995 10
996 };
997 std::thread::sleep(Duration::from_millis(interval));
998 }
999 }
1000}
1001
1002pub struct PortForwardHandle {
1007 stop: Arc<AtomicBool>,
1008 threads: Vec<std::thread::JoinHandle<()>>,
1009}
1010
1011impl Drop for PortForwardHandle {
1012 fn drop(&mut self) {
1013 self.stop.store(true, Ordering::Relaxed);
1014 for thread in self.threads.drain(..) {
1015 let _ = thread.join();
1016 }
1017 }
1018}
1019
1020fn handle_forward_connection(
1021 tcp_stream: TcpStream,
1022 vm: &VirtualMachine,
1023 guest_port: u16,
1024) -> Result<()> {
1025 let vsock_stream = open_forward_stream(vm, guest_port)?;
1026 relay(tcp_stream, vsock_stream);
1028 Ok(())
1029}
1030
1031fn open_forward_stream(vm: &VirtualMachine, guest_port: u16) -> Result<TcpStream> {
1035 let mut vsock_stream = vm
1036 .connect_to_vsock_port(VSOCK_PORT_FORWARD)
1037 .map_err(|e| anyhow::anyhow!("vsock connect for port forward: {}", e))?;
1038 let _ = vsock_stream.set_nodelay(true);
1039
1040 let req = ForwardRequest { port: guest_port };
1042 frame::send_json(&mut vsock_stream, frame::FWD_REQ, &req)?;
1043
1044 let (_msg_type, payload) = frame::read_frame(&mut vsock_stream)
1046 .context("reading forward response")?
1047 .context("guest closed connection during forward handshake")?;
1048 let resp: ForwardResponse =
1049 serde_json::from_slice(&payload).context("parsing forward response")?;
1050
1051 if resp.status != "ok" {
1052 bail!(
1053 "guest refused forward: {}",
1054 resp.message.unwrap_or_default()
1055 );
1056 }
1057
1058 Ok(vsock_stream)
1059}
1060
1061fn relay(a: TcpStream, b: TcpStream) {
1062 let mut a_read = a.try_clone().expect("clone tcp stream");
1063 let mut b_write = b.try_clone().expect("clone vsock stream");
1064 let mut b_read = b;
1065 let mut a_write = a;
1066
1067 let t1 = std::thread::spawn(move || {
1068 let _ = std::io::copy(&mut a_read, &mut b_write);
1069 let _ = b_write.shutdown(Shutdown::Write);
1070 });
1071 let t2 = std::thread::spawn(move || {
1072 let _ = std::io::copy(&mut b_read, &mut a_write);
1073 let _ = a_write.shutdown(Shutdown::Write);
1074 });
1075 let _ = t1.join();
1076 let _ = t2.join();
1077}