Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/devices/src/virtio/net/unixgram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@ use nix::sys::socket::{
use nix::unistd::unlink;
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use std::path::PathBuf;
use std::process;
use std::sync::atomic::{AtomicU32, Ordering};

use super::backend::{ConnectError, NetBackend, ReadError, WriteError};
use super::write_virtio_net_hdr;
#[cfg(target_os = "macos")]
use super::{MAX_BUFFER_SIZE, VNET_HDR_LEN};

const VFKIT_MAGIC: [u8; 4] = *b"VFKT";

/// Per-process counter to generate unique local unixgram socket filenames.
///
/// The local socket is placed in the same directory as the peer using a short
/// PID+counter name. The peer filename always contains the machine name, so it
/// is longer than our fixed-format name for any reasonably-named machine, keeping
/// the local path within macOS's 104-byte unix socket limit.
static NET_SOCK_COUNTER: AtomicU32 = AtomicU32::new(0);

const DEFAULT_SOCKET_BUF_SIZE: usize = 7 * 1024 * 1024;

// On macOS, with UNIX datagram sockets the send buffer is not used for queuing;
Expand Down Expand Up @@ -76,8 +87,16 @@ impl Unixgram {
)
.map_err(ConnectError::CreateSocket)?;
let peer_addr = UnixAddr::new(&path).map_err(ConnectError::InvalidAddress)?;
let local_addr = UnixAddr::new(&PathBuf::from(format!("{}-krun.sock", path.display())))
.map_err(ConnectError::InvalidAddress)?;
let socket_name = format!(
"krun-net-{}-{}.sock",
process::id(),
NET_SOCK_COUNTER.fetch_add(1, Ordering::Relaxed),
);
let local_path = path
.parent()
.map(|dir| dir.join(&socket_name))
.unwrap_or_else(|| std::env::temp_dir().join(&socket_name));
let local_addr = UnixAddr::new(&local_path).map_err(ConnectError::InvalidAddress)?;
if let Some(path) = local_addr.path() {
_ = unlink(path);
}
Expand Down
4 changes: 4 additions & 0 deletions tests/test_cases/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ pub fn test_cases() -> Vec<TestCase> {
TestCase::new("net-passt", Box::new(TestNet::new_passt())),
TestCase::new("net-tap", Box::new(TestNet::new_tap())),
TestCase::new("net-gvproxy", Box::new(TestNet::new_gvproxy())),
TestCase::new(
"net-gvproxy-long-path",
Box::new(TestNet::new_gvproxy_long_path()),
),
TestCase::new("net-vmnet-helper", Box::new(TestNet::new_vmnet_helper())),
TestCase::new("multiport-console", Box::new(TestMultiportConsole)),
#[cfg(any(feature = "host", target_os = "linux"))]
Expand Down
41 changes: 38 additions & 3 deletions tests/test_cases/src/test_net/gvproxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,18 @@ pub(crate) fn should_run() -> ShouldRun {
}
}

pub(crate) fn setup_backend(ctx: u32, test_setup: &TestSetup) -> anyhow::Result<()> {
fn setup_backend_with_socket(
ctx: u32,
test_setup: &TestSetup,
socket_name: &str,
log_name: &str,
) -> anyhow::Result<()> {
let tmp_dir = test_setup
.tmp_dir
.canonicalize()
.unwrap_or_else(|_| test_setup.tmp_dir.clone());
let socket_path = tmp_dir.join("gvproxy.sock");
let gvproxy_log = tmp_dir.join("gvproxy.log");
let socket_path = tmp_dir.join(socket_name);
let gvproxy_log = tmp_dir.join(log_name);

let socket_path_str = socket_path
.to_str()
Expand Down Expand Up @@ -169,3 +174,33 @@ pub(crate) fn setup_backend(ctx: u32, test_setup: &TestSetup) -> anyhow::Result<
}
Ok(())
}

pub(crate) fn setup_backend(ctx: u32, test_setup: &TestSetup) -> anyhow::Result<()> {
setup_backend_with_socket(ctx, test_setup, "gvproxy.sock", "gvproxy.log")
}

/// Backend setup with a peer socket path long enough to have previously
/// triggered ENAMETOOLONG on macOS when the local bind address was derived
/// from the peer path by appending a suffix.
pub(crate) fn setup_backend_long_path(ctx: u32, test_setup: &TestSetup) -> anyhow::Result<()> {
// Build a peer socket filename so that the full path approaches the
// 104-byte macOS unix socket limit. Use base_len measured at runtime so
// the padding is correct regardless of the exact tmp_dir length.
// tmp_dir is typically "/tmp/libkrun-tests.XXXXXXXX" (~27 chars), or
// "/private/tmp/libkrun-tests.XXXXXXXX" (~35 chars) after canonicalize on macOS.
let tmp_dir = test_setup
.tmp_dir
.canonicalize()
.unwrap_or_else(|_| test_setup.tmp_dir.clone());
let base_len = tmp_dir.to_str().map(|s| s.len()).unwrap_or(0);
const TARGET_PATH_LEN: usize = 96;
let prefix = "gvp-";
let suffix = ".sock";
let name_needed = TARGET_PATH_LEN.saturating_sub(base_len + 1);
let pad_len = name_needed
.saturating_sub(prefix.len() + suffix.len())
.max(1);
let socket_name = format!("{}{}{}", prefix, "x".repeat(pad_len), suffix);

setup_backend_with_socket(ctx, test_setup, &socket_name, "gvproxy-long-path.log")
}
14 changes: 14 additions & 0 deletions tests/test_cases/src/test_net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ impl TestNet {
cleanup: None,
}
}

/// Gvproxy backend variant with a socket path ≥ 96 bytes, triggering the
/// ENAMETOOLONG bug when the local socket was derived from the peer path.
pub fn new_gvproxy_long_path() -> Self {
Self {
tcp_tester: TcpTester::new([192, 168, 127, 254].into(), 9004),
#[cfg(feature = "host")]
should_run: gvproxy::should_run,
#[cfg(feature = "host")]
setup_backend: gvproxy::setup_backend_long_path,
#[cfg(feature = "host")]
cleanup: None,
}
}
}

#[host]
Expand Down