Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ pin-project-lite = { workspace = true, optional = true }

[target.'cfg(unix)'.dependencies]
rustix = { workspace = true, features = ["mm", "process"] }
listenfd = { workspace = true, optional = true }

[dev-dependencies]
env_logger = { workspace = true }
Expand Down Expand Up @@ -472,6 +473,7 @@ pin-project-lite = "0.2.14"
sha2 = { version = "0.10.2", default-features = false }
gdbstub = "0.7.10"
gdbstub_arch = "0.3.3"
listenfd = "1"

# =============================================================================
#
Expand Down Expand Up @@ -617,6 +619,7 @@ serve = [
"dep:http-body-util",
"dep:http",
"dep:pin-project-lite",
"dep:listenfd",
"wasmtime-cli-flags/async",
"wasmtime-wasi-http?/p2",
]
Expand Down
5 changes: 5 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ Unreleased.

### Added

- Add `--listenfd` option to `wasmtime serve`, which allows launching wasmtime
with sockets inherited from a service manager (e.g. systemd socket units).

### Changed

- Remove non-functional `listenfd` WASI CLI option.

--------------------------------------------------------------------------------

Release notes for previous releases of Wasmtime can be found on the respective
Expand Down
4 changes: 0 additions & 4 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,10 +516,6 @@ wasmtime_option_group! {
pub config: Option<bool>,
/// Enable support for WASI key-value imports (experimental)
pub keyvalue: Option<bool>,
/// Inherit environment variables and file descriptors following the
/// systemd listen fd specification (UNIX only) (legacy wasip1
/// implementation only)
pub listenfd: Option<bool>,
/// Grant access to the given TCP listen socket (experimental, legacy
/// wasip1 implementation only)
#[serde(default)]
Expand Down
110 changes: 87 additions & 23 deletions src/commands/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use pin_project_lite::pin_project;
use std::convert::Infallible;
use std::ffi::OsString;
use std::net::SocketAddr;
use std::net::TcpListener as StdTcpListener;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{
Expand All @@ -18,6 +19,7 @@ use std::{
time::{Duration, Instant},
};
use tokio::io::{self, AsyncWrite};
use tokio::net::TcpListener;
use tokio::sync::{Notify, Semaphore};
use wasmtime::component::{Component, GuestTaskId, Linker};
use wasmtime::error::Context as _;
Expand Down Expand Up @@ -121,6 +123,11 @@ pub struct ServeCommand {
#[arg(long)]
no_logging_prefix: bool,

/// Use sockets passed via the 'LISTEN_FDS' environment variable (set e.g. by systemd when
/// launching a service from socket units). Not available on Windows.
#[arg(long)]
listenfd: bool,
Comment thread
simolus3 marked this conversation as resolved.
Comment thread
simolus3 marked this conversation as resolved.

/// The WebAssembly component to run.
#[arg(value_name = "WASM", required = true)]
component: PathBuf,
Expand Down Expand Up @@ -171,6 +178,13 @@ pub struct ServeCommand {
impl ServeCommand {
/// Start a server to run the given wasi-http proxy component
pub fn execute(mut self) -> Result<()> {
let inherited_socket = if self.listenfd {
self.inherit_socket()
.context("Failed to resolve LISTEN_FDS")?
} else {
None
};

self.run.common.init_logging()?;

// We force cli errors before starting to listen for connections so then
Expand Down Expand Up @@ -201,7 +215,7 @@ impl ServeCommand {
.enable_io()
.build()?;

runtime.block_on(self.serve())?;
runtime.block_on(self.serve(inherited_socket))?;

Ok(())
}
Expand Down Expand Up @@ -310,6 +324,7 @@ impl ServeCommand {
async fn serve_under_debugger(
self,
mut debug_run: RunCommand,
inherited_socket: Option<StdTcpListener>,
linker: Linker<Host>,
component: Component,
) -> Result<()> {
Expand Down Expand Up @@ -345,7 +360,14 @@ impl ServeCommand {
&debug_component,
&mut debug_linker,
debuggee_store,
move |store| Box::pin(self.serve_maybe_debug(linker, component, Some(store))),
move |store| {
Box::pin(self.serve_maybe_debug(
linker,
inherited_socket,
component,
Some(store),
))
},
)
.await
}
Expand Down Expand Up @@ -530,7 +552,7 @@ impl ServeCommand {
Ok(())
}

async fn serve(mut self) -> Result<()> {
async fn serve(mut self, inherited_socket: Option<StdTcpListener>) -> Result<()> {
#[cfg(feature = "debug")]
let debug_run = self.debugger_setup()?;

Expand Down Expand Up @@ -567,16 +589,18 @@ impl ServeCommand {
#[cfg(feature = "debug")]
if let Some(debug_run) = debug_run {
return self
.serve_under_debugger(debug_run, linker, component)
.serve_under_debugger(debug_run, inherited_socket, linker, component)
.await;
}

self.serve_maybe_debug(linker, component, None).await
self.serve_maybe_debug(linker, inherited_socket, component, None)
.await
}

async fn serve_maybe_debug(
self,
linker: Linker<Host>,
inherited_socket: Option<StdTcpListener>,
component: Component,
mut debuggee_store: Option<&mut Store<Host>>,
) -> Result<()> {
Expand Down Expand Up @@ -615,25 +639,35 @@ impl ServeCommand {
});
}

let socket = match &self.addr {
SocketAddr::V4(_) => tokio::net::TcpSocket::new_v4()?,
SocketAddr::V6(_) => tokio::net::TcpSocket::new_v6()?,
let listener = match inherited_socket {
Some(listener) => {
eprintln!("Serving HTTP on inherited socket");
log::info!("Listening on inherited socket");

TcpListener::from_std(listener)?
}
None => {
let socket = match &self.addr {
SocketAddr::V4(_) => tokio::net::TcpSocket::new_v4()?,
SocketAddr::V6(_) => tokio::net::TcpSocket::new_v6()?,
};
// Conditionally enable `SO_REUSEADDR` depending on the current
// platform. On Unix we want this to be able to rebind an address in
// the `TIME_WAIT` state which can happen then a server is killed with
// active TCP connections and then restarted. On Windows though if
// `SO_REUSEADDR` is specified then it enables multiple applications to
// bind the port at the same time which is not something we want. Hence
// this is conditionally set based on the platform (and deviates from
// Tokio's default from always-on).
socket.set_reuseaddr(!cfg!(windows))?;
socket.bind(self.addr)?;
let listener = socket.listen(100)?;

eprintln!("Serving HTTP on http://{}/", listener.local_addr()?);
log::info!("Listening on {}", self.addr);
listener
}
};
// Conditionally enable `SO_REUSEADDR` depending on the current
// platform. On Unix we want this to be able to rebind an address in
// the `TIME_WAIT` state which can happen then a server is killed with
// active TCP connections and then restarted. On Windows though if
// `SO_REUSEADDR` is specified then it enables multiple applications to
// bind the port at the same time which is not something we want. Hence
// this is conditionally set based on the platform (and deviates from
// Tokio's default from always-on).
socket.set_reuseaddr(!cfg!(windows))?;
socket.bind(self.addr)?;
let listener = socket.listen(100)?;

eprintln!("Serving HTTP on http://{}/", listener.local_addr()?);

log::info!("Listening on {}", self.addr);

let epoch_interval = if let Some(Profile::Guest { interval, .. }) = self.run.profile {
Some(interval)
Expand Down Expand Up @@ -753,6 +787,36 @@ impl ServeCommand {

Ok(())
}

/// Attempts to find the first `AF_INET` socket passed to this process via the
/// [protocol used by systemd](https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html#Notes).
Comment thread
simolus3 marked this conversation as resolved.
Outdated
///
/// This is most commonly used for systemd [socket activation units](https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html),
/// which makes systemd create a socket and launch wasmtime on the first connection to it. This
/// allows sandboxing the wasmtime process in e.g. a private network namespace.
#[cfg(unix)]
fn inherit_socket(&self) -> Result<Option<StdTcpListener>> {
use listenfd::ListenFd;
Comment thread
simolus3 marked this conversation as resolved.
Outdated

let mut listenfd = ListenFd::from_env();
Comment thread
simolus3 marked this conversation as resolved.
Outdated

for i in 0..listenfd.len() {
let Ok(Some(listener)) = listenfd.take_tcp_listener(i) else {
continue;
};

listener.set_nonblocking(true)?;
return Ok(Some(listener));
}

eprintln!("--listenfd enabled, but no socket was passed by the system manager.");
Ok(None)
}

#[cfg(not(unix))]
fn inherit_socket(&self) -> Result<Option<StdTcpListener>> {
bail!("The --listenfd option is not available on Windows.")
}
}

pin_project! {
Expand Down
3 changes: 0 additions & 3 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,6 @@ impl RunCommon {
builder.initial_cwd(cwd);
}

if self.common.wasi.listenfd == Some(true) {
bail!("components do not support --listenfd");
}
for _ in self.compute_preopen_sockets()? {
bail!("components do not support --tcplisten");
}
Expand Down
Loading