summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2020-10-23 16:11:37 +0000
committerAlyssa Ross <hi@alyssa.is>2020-10-23 16:11:37 +0000
commit227fc065bd9eb0750a7f0f92321d505ddc113a2e (patch)
tree80f4c6ede501bf845b7a69810c5fa28ef03976a1
parent1def890e17c9a416a5728e4c13e60b79c01bbdd2 (diff)
downloadspectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar
spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.gz
spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.bz2
spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.lz
spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.xz
spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.zst
spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.zip
WIP to show Shell HEAD master
-rw-r--r--getopt/lib.rs2
-rw-r--r--getopt_sys/lib.rs2
-rw-r--r--libmnl_sys/lib.rs2
-rw-r--r--linux_sys/build.rs7
-rw-r--r--linux_sys/lib.rs51
-rw-r--r--linux_sys/wrapper.h3
-rw-r--r--shell.nix14
-rw-r--r--src/ip/v4/cidr.rs2
-rw-r--r--src/mac_address.rs2
-rw-r--r--src/main.rs111
-rw-r--r--src/usb.rs5
-rw-r--r--src/vm_config.rs2
-rw-r--r--src/vsock.rs206
13 files changed, 365 insertions, 44 deletions
diff --git a/getopt/lib.rs b/getopt/lib.rs
index 2ffe57d..09ec0a3 100644
--- a/getopt/lib.rs
+++ b/getopt/lib.rs
@@ -11,7 +11,7 @@ use std::os::raw::{c_char, c_int};
 use std::os::unix::ffi::{OsStrExt, OsStringExt};
 use std::ptr::null_mut;
 
-use getopt_sys::{optind, optarg, getopt_long, option};
+use getopt_sys::{getopt_long, optarg, optind, option};
 
 fn c_to_os_string(s: CString) -> OsString {
     OsString::from_vec(s.into_bytes())
diff --git a/getopt_sys/lib.rs b/getopt_sys/lib.rs
index 7393693..a820df6 100644
--- a/getopt_sys/lib.rs
+++ b/getopt_sys/lib.rs
@@ -38,6 +38,6 @@ extern "C" {
         argv: *mut *const c_char,
         optstring: *const c_char,
         longopts: *const option,
-        longindex: *mut c_int,        
+        longindex: *mut c_int,
     );
 }
diff --git a/libmnl_sys/lib.rs b/libmnl_sys/lib.rs
index b80da54..ddfb761 100644
--- a/libmnl_sys/lib.rs
+++ b/libmnl_sys/lib.rs
@@ -13,8 +13,8 @@ mod generated {
 
 pub use generated::*;
 
-use std::os::raw::c_long;
 use std::cmp::min;
+use std::os::raw::c_long;
 
 // MNL_SOCKET_BUFFER_SIZE is a preprocessor macro that calls sysconf,
 // and so can't be translated into a Rust constant.
diff --git a/linux_sys/build.rs b/linux_sys/build.rs
index 76af123..d5ae44c 100644
--- a/linux_sys/build.rs
+++ b/linux_sys/build.rs
@@ -1,7 +1,7 @@
 extern crate bindgen;
 
-use std::path::PathBuf;
 use std::env;
+use std::path::PathBuf;
 
 fn main() {
     println!("cargo:rerun-if-changed=wrapper.h");
@@ -9,6 +9,11 @@ fn main() {
     let bindings = bindgen::Builder::default()
         .header("wrapper.h")
         .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+        // These functions generate bindings that use u128, which are not valid for
+        // FFI.  We don't need them anyway, so just disable them to prevent the warnings.
+        // See <https://github.com/rust-lang/rust-bindgen/issues/1560>.
+        .blacklist_function("q[efg]cvt(_r)?")
+        .blacklist_function("strtold")
         .prepend_enum_name(false)
         .generate()
         .expect("Unable to generate bindings");
diff --git a/linux_sys/lib.rs b/linux_sys/lib.rs
index 7f7fa1a..ed0e390 100644
--- a/linux_sys/lib.rs
+++ b/linux_sys/lib.rs
@@ -14,33 +14,40 @@ mod generated {
 
 pub use generated::*;
 
-macro_rules! _IOC {
-    ($dir: expr, $type_: expr, $nr: expr, $size: expr) => {
-        (($dir as u32) << _IOC_DIRSHIFT)
-            | (($type_ as u32) << _IOC_TYPESHIFT)
-            | (($nr as u32) << _IOC_NRSHIFT)
-            | (($size as u32) << _IOC_SIZESHIFT)
-    };
+// Function-like macros bindgen can't bind to.
+
+pub const fn _IOC_TYPECHECK<size>() -> usize {
+    std::mem::size_of::<size>()
+}
+
+pub const fn _IOC(dir: u32, type_: u8, nr: u32, size: usize) -> u32 {
+    (dir << _IOC_DIRSHIFT)
+        | ((type_ as u32) << _IOC_TYPESHIFT)
+        | (nr << _IOC_NRSHIFT)
+        | ((size as u32) << _IOC_SIZESHIFT)
+}
+
+pub const fn _IOW<size>(type_: u8, nr: u32) -> u32 {
+    _IOC(_IOC_WRITE, type_, nr, _IOC_TYPECHECK::<size>())
+}
+
+pub const fn _IOR<size>(type_: u8, nr: u32) -> u32 {
+    _IOC(_IOC_READ, type_, nr, _IOC_TYPECHECK::<size>())
 }
 
-macro_rules! _IOC_TYPECHECK {
-    ($t: ty) => {
-        ::std::mem::size_of::<$t>()
-    };
+pub const fn WEXITSTATUS(status: i32) -> i8 {
+    ((status & 0xff00) >> 8) as _
 }
 
-macro_rules! _IOW {
-    ($type_: expr, $nr: expr, $size: ty) => {
-        _IOC!(_IOC_WRITE, $type_, $nr, _IOC_TYPECHECK!($size))
-    };
+pub const fn WTERMSIG(status: i32) -> i8 {
+    (status & 0x7f) as _
 }
 
-macro_rules! _IOR {
-    ($type_: expr, $nr: expr, $size: ty) => {
-        _IOC!(_IOC_READ, $type_, $nr, _IOC_TYPECHECK!($size))
-    };
+pub const fn WIFEXITED(status: i32) -> bool {
+    WTERMSIG(status) == 0
 }
 
-// Extras missed by bindgen:
-pub const TUNSETIFF: u32 = _IOW!(b'T', 202, std::os::raw::c_int);
-pub const TUNGETIFF: u32 = _IOR!(b'T', 210, std::os::raw::c_uint);
+// Extras values bindgen can't translate.
+
+pub const TUNSETIFF: u32 = _IOW::<std::os::raw::c_int>(b'T', 202);
+pub const TUNGETIFF: u32 = _IOR::<std::os::raw::c_uint>(b'T', 210);
diff --git a/linux_sys/wrapper.h b/linux_sys/wrapper.h
index 99ff6a9..447137f 100644
--- a/linux_sys/wrapper.h
+++ b/linux_sys/wrapper.h
@@ -1,7 +1,9 @@
 #include <arpa/inet.h>
 #include <fcntl.h>
 #include <net/if.h>
+#include <stdlib.h>
 #include <sys/ioctl.h>
+#include <sys/wait.h>
 #include <sysexits.h>
 #include <time.h>
 #include <unistd.h>
@@ -9,3 +11,4 @@
 #include <linux/if.h>
 #include <linux/if_tun.h>
 #include <linux/rtnetlink.h>
+#include <linux/vm_sockets.h>
diff --git a/shell.nix b/shell.nix
index 5969246..1327441 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,7 +1,13 @@
 { pkgs ? import <nixpkgs> {} } @ args:
 with pkgs;
 
-(import /home/src/nixlib/shells/rust.nix args).overrideAttrs ({ buildInputs ? [], ... }: {
-  name = "crosvm-shell";
-  buildInputs = buildInputs ++ [ linuxHeaders libmnl ];
-})
+mkShell {
+  buildInputs = [ cargo libmnl clang llvm rustfmt ];
+
+  LIBCLANG_PATH = "${llvmPackages.libclang}/lib";
+  }
+
+# (import /home/src/nixlib/shells/rust.nix args).overrideAttrs ({ buildInputs ? [], ... }: {
+#   name = "crosvm-shell";
+#   buildInputs = buildInputs ++ [ linuxHeaders libmnl ];
+# })
diff --git a/src/ip/v4/cidr.rs b/src/ip/v4/cidr.rs
index 83ad45e..6e3aecd 100644
--- a/src/ip/v4/cidr.rs
+++ b/src/ip/v4/cidr.rs
@@ -78,7 +78,7 @@ impl CidrBlock {
 
         let prefix_len = prefix_str.parse().map_err(|_| prefix_err(prefix_bytes))?;
         if prefix_len > 32 {
-            return Err(prefix_err(prefix_bytes))
+            return Err(prefix_err(prefix_bytes));
         }
 
         Ok(Self { addr, prefix_len })
diff --git a/src/mac_address.rs b/src/mac_address.rs
index f35939a..0c923cb 100644
--- a/src/mac_address.rs
+++ b/src/mac_address.rs
@@ -1,4 +1,4 @@
-use std::fmt::{self, Display, Formatter, Debug};
+use std::fmt::{self, Debug, Display, Formatter};
 
 #[derive(Copy, Clone, Eq, PartialEq)]
 pub struct MacAddress([u8; 6]);
diff --git a/src/main.rs b/src/main.rs
index 5da628d..dd8a76a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,12 +5,14 @@ mod tap;
 mod usb;
 mod vm_config;
 mod vm_name;
+mod vsock;
 
 use std::env::args_os;
 use std::ffi::{OsStr, OsString};
 use std::fmt::{self, Display, Formatter};
 use std::fs::File;
-use std::io::{self, stderr, Read, Write};
+use std::num::NonZeroI32;
+use std::io::{self, stderr, stdin, ErrorKind, Read, Write};
 use std::mem::forget;
 use std::os::unix::prelude::*;
 use std::path::{Path, PathBuf};
@@ -18,7 +20,10 @@ use std::process::{exit, Command};
 
 use getopt::{GetoptArgs, GetoptLong, LongOptDef, OptDef, ShortOptDef};
 
-use linux_sys::{EX_OSERR, EX_UNAVAILABLE, EX_USAGE};
+use linux_sys::{
+    fcntl, waitpid, EXIT_SUCCESS, EX_OSERR, EX_UNAVAILABLE, EX_USAGE, F_GETFD, WEXITED,
+    WEXITSTATUS, WIFEXITED,
+};
 
 use err::{eprint_argv0, OrExit};
 use mac_address::MacAddress;
@@ -30,16 +35,65 @@ use vm_name::VmName;
 #[derive(Clone)]
 enum Opt {
     Live(OsString),
+    NotifyFd(OsString),
     Usb(OsString),
 }
 
-const OPT_DEFS: [OptDef<Opt>; 4] = [
+const OPT_DEFS: [OptDef<Opt>; 6] = [
     OptDef::Short(ShortOptDef::RequiredArgument('l', &Opt::Live)),
     OptDef::Long(LongOptDef::RequiredArgument("runtime-dir", &Opt::Live)),
+    OptDef::Short(ShortOptDef::RequiredArgument('d', &Opt::NotifyFd)),
+    OptDef::Long(LongOptDef::RequiredArgument("notify-fd", &Opt::NotifyFd)),
     OptDef::Short(ShortOptDef::RequiredArgument('u', &Opt::Usb)),
     OptDef::Long(LongOptDef::RequiredArgument("usb", &Opt::Usb)),
 ];
 
+enum ForkResult {
+    Child,
+    Parent(NonZeroI32),
+}
+
+unsafe fn fork() -> io::Result<ForkResult> {
+    match linux_sys::fork() {
+        -1 => Err(io::Error::last_os_error()),
+        0 => Ok(ForkResult::Child),
+        pid => Ok(ForkResult::Parent(NonZeroI32::new_unchecked(pid)))
+    }
+}
+
+enum DoubleForkResult {
+    Child,
+    Parent,
+}
+
+unsafe fn double_fork() -> io::Result<DoubleForkResult> {
+    match fork()? {
+        ForkResult::Child => match fork() {
+            Err(e) => exit(e.raw_os_error().unwrap()),
+            Ok(ForkResult::Child) => Ok(DoubleForkResult::Child),
+            Ok(ForkResult::Parent(_)) => exit(EXIT_SUCCESS as _),
+        },
+        ForkResult::Parent(pid) => {
+            let mut wstatus = 0;
+            if waitpid(pid.get(), &mut wstatus, WEXITED as _) == -1 {
+                return Err(io::Error::last_os_error());
+            }
+
+            if !WIFEXITED(wstatus) {
+                return Err(io::Error::new(
+                    ErrorKind::Interrupted,
+                    "forked process died from signal",
+                ));
+            }
+
+            match WEXITSTATUS(wstatus) {
+                0 => Ok(DoubleForkResult::Parent),
+                errno => Err(io::Error::from_raw_os_error(errno as _)),
+            }
+        }
+    }
+}
+
 fn mac_for_ip(addr: ip::v4::Address) -> MacAddress {
     let bytes = addr.bytes();
     // OR with 1 because the guest's address will be the second address in the /31, and
@@ -64,12 +118,22 @@ impl Display for SubprocessFailed {
 
 impl std::error::Error for SubprocessFailed {}
 
-fn start_service(runtime_dir: &Path, name: &OsStr) -> Result<(), Box<dyn std::error::Error>> {
-    let s6_rc_dir = runtime_dir.join("s6-rc");
+fn s6_rc_dir(runtime_dir: &Path) -> PathBuf {
+    let mut dir = runtime_dir.join("s6-rc");
+    dir.push("live");
+    dir
+}
 
+fn service_dir(runtime_dir: &Path) -> PathBuf {
+    let mut dir = s6_rc_dir(runtime_dir);
+    dir.push("servicedirs");
+    dir
+}
+
+fn start_service(runtime_dir: &Path, name: &OsStr) -> Result<(), Box<dyn std::error::Error>> {
     let status = Command::new("s6-rc")
         .arg("-l")
-        .arg(s6_rc_dir)
+        .arg(s6_rc_dir(runtime_dir))
         .arg("-u")
         .arg("change")
         .arg(name)
@@ -92,7 +156,8 @@ fn ip_for_vm(
     runtime_dir: &Path,
     name: &VmName,
 ) -> Result<ip::v4::Address, Box<dyn std::error::Error>> {
-    let mut path = runtime_dir.join(&vm_service_name(name));
+    let mut path = service_dir(runtime_dir);
+    path.push(&vm_service_name(name));
     path.push("data");
     path.push("hostip");
 
@@ -119,8 +184,9 @@ pub fn main() {
     // Our option definitions are known not to contain null bytes.
     let mut gl = GetoptLong::new(OPT_DEFS.to_vec(), args).unwrap();
 
-    let mut usb_resources = vec![];
+    let mut notify_fd: Option<File> = None;
     let mut runtime_dir: Option<PathBuf> = None;
+    let mut usb_resources = vec![];
 
     while let Some(item) = unsafe { gl.next() }.unwrap_or_exit(EX_USAGE as _) {
         match item {
@@ -129,6 +195,25 @@ pub fn main() {
                     UsbResource::parse(s).expect_or_exit(EX_USAGE as _, "parsing USB resource");
                 usb_resources.push(resource);
             }
+            Opt::NotifyFd(fd) => {
+                let fd: RawFd = fd
+                    .into_string()
+                    .map_err(|x| x.to_string_lossy().into_owned())
+                    .expect_or_exit(EX_USAGE as _, "parsing notify fd")
+                    .parse()
+                    .expect_or_exit(EX_USAGE as _, "parsing notify fd");
+
+                // Check the fd is valid.
+                // Safe because the fd was given to us by the user.
+                if unsafe { fcntl(fd, F_GETFD as _) } == -1 {
+                    eprint_argv0();
+                    eprint!("notify fd: {}", io::Error::last_os_error());
+                    exit(EX_USAGE as _);
+                }
+
+                // Safe because fd was given to us by the user, and we're taking ownership of it.
+                notify_fd = Some(unsafe { File::from_raw_fd(fd) });
+            }
             Opt::Live(dir) => {
                 runtime_dir = Some(dir.into());
             }
@@ -169,7 +254,7 @@ pub fn main() {
 
     dbg!(&usb_resources);
 
-    let mut args = vec![OsString::from("crosvm"), "run".into()];
+    let mut args = vec![OsString::from("run")];
 
     let config = VmConfig::new(path);
 
@@ -190,8 +275,10 @@ pub fn main() {
         forget(tap);
     }
 
+    args.push("-p--".into());
+
     if !usb_resources.is_empty() {
-        let mut param = OsString::from("spectrum.usb=");
+        let mut param = OsString::from("usb=");
 
         for (i, res) in usb_resources.into_iter().enumerate() {
             start_service(&runtime_dir, &vm_service_name(&res.vm))
@@ -218,4 +305,8 @@ pub fn main() {
     args.push(config.kernel_path().into());
 
     println!("{:?}", args);
+
+    dbg!(stdin().read_line(&mut String::new()));
+
+    dbg!(Command::new("crosvm").args(args).exec());
 }
diff --git a/src/usb.rs b/src/usb.rs
index 35293e1..91d5fa4 100644
--- a/src/usb.rs
+++ b/src/usb.rs
@@ -76,7 +76,10 @@ mod tests {
     #[test]
     fn resource_parse_missing_vm() {
         let result = UsbResource::parse(":b".into());
-        assert!(matches!(result, Err(UsbResourceParseError::Vm(vm_name::Error::Empty))));
+        assert!(matches!(
+            result,
+            Err(UsbResourceParseError::Vm(vm_name::Error::Empty))
+        ));
     }
 
     #[test]
diff --git a/src/vm_config.rs b/src/vm_config.rs
index 339e880..8f1333c 100644
--- a/src/vm_config.rs
+++ b/src/vm_config.rs
@@ -79,6 +79,6 @@ impl<'a> VmConfig<'a> {
     }
 
     pub fn kernel_path(&self) -> PathBuf {
-        self.path.join("bzImage")
+        self.path.join("kernel")
     }
 }
diff --git a/src/vsock.rs b/src/vsock.rs
new file mode 100644
index 0000000..d39675c
--- /dev/null
+++ b/src/vsock.rs
@@ -0,0 +1,206 @@
+use std::io;
+use std::io::prelude::*;
+use std::mem::{size_of, zeroed};
+use std::os::unix::prelude::*;
+
+use linux_sys::{
+    accept, bind, close, connect, listen, read, sockaddr, sockaddr_vm, socket, socklen_t, write,
+    AF_VSOCK, SOCK_STREAM,
+};
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct VsockAddr {
+    pub cid: u32,
+    pub port: u32,
+}
+
+impl From<VsockAddr> for sockaddr_vm {
+    fn from(addr: VsockAddr) -> Self {
+        // Safe because a zeroed sockaddr_vm is valid.
+        let mut sa: sockaddr_vm = unsafe { zeroed() };
+        sa.svm_family = AF_VSOCK as _;
+        sa.svm_port = addr.port;
+        sa.svm_cid = addr.cid;
+        sa
+    }
+}
+
+impl From<sockaddr_vm> for VsockAddr {
+    fn from(addr: sockaddr_vm) -> Self {
+        Self {
+            cid: addr.svm_cid,
+            port: addr.svm_port,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct VsockStream(RawFd);
+
+impl VsockStream {
+    fn new() -> io::Result<Self> {
+        // Safe because arguments are static and we check the return value.
+        let fd = unsafe { socket(AF_VSOCK as _, SOCK_STREAM as _, 0) };
+        if fd == -1 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(Self(fd))
+    }
+
+    pub fn connect(addr: VsockAddr) -> io::Result<Self> {
+        let sock = Self::new()?;
+
+        // Safe because fd is valid and owned by us, the address is owned by us, and we
+        // pass the correct length for the address.
+        if unsafe {
+            connect(
+                sock.as_raw_fd(),
+                &sockaddr_vm::from(addr) as *const _ as *const sockaddr,
+                size_of::<sockaddr_vm>() as _,
+            )
+        } == -1
+        {
+            return Err(io::Error::last_os_error());
+        }
+
+        return Ok(sock);
+    }
+}
+
+impl Read for VsockStream {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        // Safe because fd is valid and owned by us, buf is borrowed by us, and we pass the
+        // correct length for buf.
+        let len = unsafe { read(self.0, buf as *mut [u8] as *mut _, buf.len() as _) };
+        if len == -1 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(len as _)
+    }
+}
+
+impl Write for VsockStream {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        // Safe because fd is valid and owned by us, buf is borrowed by us, and we pass the
+        // correct length for buf.
+        let len = unsafe { write(self.0, buf as *const [u8] as *const _, buf.len() as _) };
+        if len == -1 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(len as _)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+impl AsRawFd for VsockStream {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0
+    }
+}
+
+impl FromRawFd for VsockStream {
+    unsafe fn from_raw_fd(fd: RawFd) -> Self {
+        Self(fd)
+    }
+}
+
+impl IntoRawFd for VsockStream {
+    fn into_raw_fd(self) -> RawFd {
+        self.0
+    }
+}
+
+impl Drop for VsockStream {
+    fn drop(&mut self) {
+        // Safe because we pass a valid FD, owned by us.
+        let _ = unsafe { close(self.0) };
+    }
+}
+
+#[derive(Debug)]
+pub struct VsockListener(RawFd);
+
+impl VsockListener {
+    fn new() -> io::Result<Self> {
+        // Safe because arguments are static and we check the return value.
+        let fd = unsafe { socket(AF_VSOCK as _, SOCK_STREAM as _, 0) };
+        if fd == -1 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(Self(fd))
+    }
+
+    pub fn bind(addr: VsockAddr) -> io::Result<Self> {
+        let sock = VsockListener::new()?;
+
+        let sa = sockaddr_vm::from(addr);
+
+        // Safe because fd is valid and owned by us, addr is owned by us, and we pass the
+        // correct size.
+        if unsafe {
+            bind(
+                sock.as_raw_fd(),
+                &sa as *const _ as *const sockaddr,
+                size_of::<sockaddr_vm>() as _,
+            )
+        } == -1
+        {
+            return Err(io::Error::last_os_error());
+        }
+
+        // Safe because fd is valid and owned by us.
+        if unsafe { listen(sock.as_raw_fd(), 128) } == -1 {
+            return Err(io::Error::last_os_error());
+        }
+
+        return Ok(sock);
+    }
+
+    pub fn accept(&self) -> io::Result<(VsockStream, VsockAddr)> {
+        // Safe because this is an out parameter.
+        let mut addr: sockaddr_vm = unsafe { zeroed() };
+        let mut addr_len = size_of::<sockaddr_vm>() as socklen_t;
+
+        // Safe because fd is valid and owned by us, addr is valid and owned by us, and we
+        // pass the correct length, and we check the return value.
+        let fd = unsafe { accept(self.0, &mut addr as *mut _ as *mut sockaddr, &mut addr_len) };
+        if fd == -1 {
+            return Err(io::Error::last_os_error());
+        }
+
+        // Safe because fd is valid and owned by us, and we know it's a vsock stream.
+        Ok((unsafe { VsockStream::from_raw_fd(fd) }, addr.into()))
+    }
+}
+
+impl AsRawFd for VsockListener {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0
+    }
+}
+
+impl FromRawFd for VsockListener {
+    unsafe fn from_raw_fd(fd: RawFd) -> Self {
+        Self(fd)
+    }
+}
+
+impl IntoRawFd for VsockListener {
+    fn into_raw_fd(self) -> RawFd {
+        self.0
+    }
+}
+
+impl Drop for VsockListener {
+    fn drop(&mut self) {
+        // Safe because we pass a valid FD, owned by us.
+        let _ = unsafe { close(self.0) };
+    }
+}