Merge branch 'ventureoo/systemd-socket-activation'
Some checks failed
Main / Init (push) Has been cancelled
Main / Lint (push) Has been cancelled
Main / Test (push) Has been cancelled
Main / Package (push) Has been cancelled
Main / Publish (push) Has been cancelled
Update .rpm SPEC files / update (push) Has been cancelled

This commit is contained in:
Jason Volk
2026-03-10 02:37:16 +00:00
4 changed files with 104 additions and 46 deletions

View File

@@ -3,7 +3,10 @@ mod plain;
mod tls;
mod unix;
use std::sync::{Arc, atomic::Ordering};
use std::{
net::{SocketAddr, TcpListener},
sync::{Arc, atomic::Ordering},
};
use tokio::task::JoinSet;
use tuwunel_core::{Result, debug_info};
@@ -29,24 +32,42 @@ pub(super) async fn serve(services: Arc<Services>, handle: ServerHandle) -> Resu
.await?;
}
let addrs = config.get_bind_addrs();
if !addrs.is_empty() {
#[cfg_attr(not(feature = "direct_tls"), expect(clippy::redundant_else))]
if config.tls.certs.is_some() {
#[cfg(feature = "direct_tls")]
{
services.globals.init_rustls_provider()?;
tls::serve(server, &app, &handle.handle_ip, &mut join_set, &addrs).await?;
}
let systemd_listeners: Vec<_> = systemd_listeners().collect();
let systemd_listeners_is_empty = systemd_listeners.is_empty();
let (listeners, addrs): (Vec<_>, Vec<_>) = config
.get_bind_addrs()
.into_iter()
.filter(|_| systemd_listeners_is_empty)
.map(|addr| {
let listener = TcpListener::bind(addr)
.expect("Failed to bind configured TcpListener to {addr:?}");
#[cfg(not(feature = "direct_tls"))]
return tuwunel_core::Err!(Config(
"tls",
"tuwunel was not built with direct TLS support (\"direct_tls\")"
));
} else {
plain::serve(server, &app, &handle.handle_ip, &mut join_set, &addrs);
(listener, addr)
})
.chain(systemd_listeners)
.inspect(|(listener, _)| {
listener
.set_nonblocking(true)
.expect("Failed to set non-blocking");
})
.unzip();
#[cfg_attr(not(feature = "direct_tls"), expect(clippy::redundant_else))]
if config.tls.certs.is_some() {
#[cfg(feature = "direct_tls")]
{
services.globals.init_rustls_provider()?;
tls::serve(server, &app, &handle.handle_ip, &mut join_set, &listeners, &addrs)
.await?;
}
#[cfg(not(feature = "direct_tls"))]
return tuwunel_core::Err!(Config(
"tls",
"tuwunel was not built with direct TLS support (\"direct_tls\")"
));
} else {
plain::serve(server, &app, &handle.handle_ip, &mut join_set, &listeners, &addrs)?;
}
assert!(!join_set.is_empty(), "at least one listener should be installed");
@@ -75,3 +96,27 @@ pub(super) async fn serve(services: Arc<Services>, handle: ServerHandle) -> Resu
Ok(())
}
#[cfg(all(feature = "systemd", target_os = "linux"))]
fn systemd_listeners() -> impl Iterator<Item = (TcpListener, SocketAddr)> {
sd_notify::listen_fds()
.into_iter()
.flatten()
.filter_map(|fd| {
use std::os::fd::FromRawFd;
debug_assert!(fd >= 3, "fdno probably not a listener socket");
// SAFETY: systemd should already take care of providing
// the correct TCP socket, so we just use it via raw fd
let listener = unsafe { TcpListener::from_raw_fd(fd) };
let Ok(addr) = listener.local_addr() else {
return None;
};
Some((listener, addr))
})
}
#[cfg(any(not(feature = "systemd"), not(target_os = "linux")))]
fn systemd_listeners() -> impl Iterator<Item = (TcpListener, SocketAddr)> { std::iter::empty() }

View File

@@ -1,26 +1,34 @@
use std::{net::SocketAddr, sync::Arc};
use std::{
net::{SocketAddr, TcpListener},
sync::Arc,
};
use axum::Router;
use axum_server::Handle;
use tokio::task::JoinSet;
use tuwunel_core::{Server, info};
use tuwunel_core::{Result, Server, info};
pub(super) fn serve(
server: &Arc<Server>,
router: &Router,
handle: &Handle<SocketAddr>,
join_set: &mut JoinSet<Result<(), std::io::Error>>,
listeners: &[TcpListener],
addrs: &[SocketAddr],
) {
) -> Result {
let router = router
.clone()
.into_make_service_with_connect_info::<SocketAddr>();
for addr in addrs {
let acceptor = axum_server::bind(*addr)
for listener in listeners {
let acceptor = axum_server::from_tcp(listener.try_clone()?)?
.handle(handle.clone())
.serve(router.clone());
join_set.spawn_on(acceptor, server.runtime());
}
info!("Listening on {addrs:?}");
Ok(())
}

View File

@@ -1,4 +1,7 @@
use std::{net::SocketAddr, sync::Arc};
use std::{
net::{SocketAddr, TcpListener},
sync::Arc,
};
use axum::Router;
use axum_server::Handle;
@@ -11,6 +14,7 @@ pub(super) async fn serve(
app: &Router,
handle: &Handle<SocketAddr>,
join_set: &mut JoinSet<core::result::Result<(), std::io::Error>>,
listeners: &[TcpListener],
addrs: &[SocketAddr],
) -> Result {
let tls = &server.config.tls;
@@ -43,14 +47,16 @@ pub(super) async fn serve(
.into_make_service_with_connect_info::<SocketAddr>();
if tls.dual_protocol {
for addr in addrs {
join_set.spawn_on(
axum_server_dual_protocol::bind_dual_protocol(*addr, conf.clone())
.set_upgrade(false)
.handle(handle.clone())
.serve(app.clone()),
server.runtime(),
);
for listener in listeners {
let acceptor = axum_server_dual_protocol::from_tcp_dual_protocol(
listener.try_clone()?,
conf.clone(),
)?
.set_upgrade(false)
.handle(handle.clone())
.serve(app.clone());
join_set.spawn_on(acceptor, server.runtime());
}
warn!(
@@ -58,13 +64,12 @@ pub(super) async fn serve(
(HTTP) connections too (insecure!)",
);
} else {
for addr in addrs {
join_set.spawn_on(
axum_server::bind_rustls(*addr, conf.clone())
.handle(handle.clone())
.serve(app.clone()),
server.runtime(),
);
for listener in listeners {
let acceptor = axum_server::from_tcp_rustls(listener.try_clone()?, conf.clone())?
.handle(handle.clone())
.serve(app.clone());
join_set.spawn_on(acceptor, server.runtime());
}
info!("Listening on {addrs:?} with TLS certificate {certs}");