#![cfg_attr(docsrs_alt, feature(doc_cfg))]
#![warn(missing_docs)]
#![allow(clippy::useless_conversion)]
#![doc = document_features::document_features!()]
#![cfg_attr(not(feature = "sd_listen"), deny(unsafe_code))]
#![cfg_attr(
not(feature = "default"),
allow(unused_imports, irrefutable_let_patterns, unused_variables)
)]
#[allow(unused_imports)]
use std::{
ffi::c_int,
fmt::Display,
net::SocketAddr,
path::PathBuf,
pin::Pin,
str::FromStr,
sync::Arc,
task::{ready, Context, Poll},
time::Duration,
};
#[cfg(unix)]
use std::os::fd::RawFd;
use futures_core::{Future, Stream};
#[cfg(feature = "inetd")]
use futures_util::{future::Fuse, FutureExt};
use pin_project::pin_project;
use tokio::{
io::{AsyncRead, AsyncWrite, Stdin, Stdout},
net::{TcpListener, TcpStream},
sync::oneshot::{channel, Receiver, Sender},
time::Sleep,
};
use tracing::{debug, error, info, trace, warn};
#[cfg(unix)]
use tokio::net::{UnixListener, UnixStream};
#[cfg_attr(docsrs_alt, doc(cfg(feature = "unix_path_tools")))]
#[cfg(feature = "unix_path_tools")]
#[non_exhaustive]
#[cfg_attr(
feature = "serde",
derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UnixChmodVariant {
Owner,
Group,
Everybody,
}
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "unix_path_tools")))]
impl Display for UnixChmodVariant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnixChmodVariant::Owner => "owner".fmt(f),
UnixChmodVariant::Group => "group".fmt(f),
UnixChmodVariant::Everybody => "everybody".fmt(f),
}
}
}
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "unix_path_tools")))]
impl FromStr for UnixChmodVariant {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("owner") {
Ok(UnixChmodVariant::Owner)
} else if s.eq_ignore_ascii_case("group") {
Ok(UnixChmodVariant::Group)
} else if s.eq_ignore_ascii_case("everybody") {
Ok(UnixChmodVariant::Everybody)
} else {
Err("Unknown chmod variant. Expected `owner`, `group` or `everybody`.")
}
}
}
#[cfg(feature = "socket_options")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "socket_options")))]
#[cfg_attr(
feature = "serde",
derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TcpKeepaliveParams {
pub timeout_ms: Option<u32>,
pub count: Option<u32>,
pub interval_ms: Option<u32>,
}
#[cfg(feature = "socket_options")]
impl Display for TcpKeepaliveParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(x) = self.timeout_ms {
x.fmt(f)?;
}
':'.fmt(f)?;
if let Some(x) = self.count {
x.fmt(f)?;
}
':'.fmt(f)?;
if let Some(x) = self.interval_ms {
x.fmt(f)?;
}
Ok(())
}
}
#[cfg(feature = "socket_options")]
impl FromStr for TcpKeepaliveParams {
type Err = &'static str;
#[allow(clippy::get_first)]
fn from_str(input_string: &str) -> Result<Self, Self::Err> {
let input_chunks: Vec<&str> = input_string.split(':').collect();
if input_chunks.len() > 3 {
return Err("Too many colon-separated chunks");
}
let timeout_chunk = input_chunks.get(0).unwrap_or(&"").trim();
let ping_count_chunk = input_chunks.get(1).unwrap_or(&"").trim();
let interval_chunk = input_chunks.get(2).unwrap_or(&"").trim();
let mut ka_params = TcpKeepaliveParams::default();
if !timeout_chunk.is_empty() {
ka_params.timeout_ms = Some(
timeout_chunk
.parse()
.map_err(|_| "failed to parse timeout as a number")?,
);
}
if !ping_count_chunk.is_empty() {
ka_params.count = Some(
ping_count_chunk
.parse()
.map_err(|_| "failed to parse count as a number")?,
);
}
if !interval_chunk.is_empty() {
ka_params.interval_ms = Some(
interval_chunk
.parse()
.map_err(|_| "failed to parse interval as a number")?,
);
}
Ok(ka_params)
}
}
#[cfg(feature = "socket_options")]
impl TcpKeepaliveParams {
#[must_use]
pub fn to_socket2(&self) -> socket2::TcpKeepalive {
let mut k = socket2::TcpKeepalive::new();
if let Some(x) = self.timeout_ms {
k = k.with_time(Duration::from_millis(u64::from(x)));
}
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "tvos",
target_os = "watchos",
))]
if let Some(x) = self.count {
k = k.with_retries(x);
}
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "tvos",
target_os = "watchos",
target_os = "windows",
))]
if let Some(x) = self.interval_ms {
k = k.with_interval(Duration::from_millis(u64::from(x)));
}
k
}
}
#[cfg_attr(feature = "clap", derive(clap::Args))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct UserOptions {
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "unix_path_tools")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
pub unix_listen_unlink: bool,
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "unix_path_tools")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
#[cfg_attr(feature = "serde", serde(default))]
pub unix_listen_chmod: Option<UnixChmodVariant>,
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "unix_path_tools")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
pub unix_listen_uid: Option<u32>,
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "unix_path_tools")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
pub unix_listen_gid: Option<u32>,
#[cfg(feature = "sd_listen")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "sd_listen")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
pub sd_accept_ignore_environment: bool,
#[cfg(feature = "socket_options")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "socket_options")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub tcp_keepalive: Option<TcpKeepaliveParams>,
#[cfg(feature = "socket_options")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "socket_options")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub tcp_reuse_port: bool,
#[cfg(feature = "socket_options")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "socket_options")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub recv_buffer_size: Option<usize>,
#[cfg(feature = "socket_options")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "socket_options")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub send_buffer_size: Option<usize>,
#[cfg(feature = "socket_options")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "socket_options")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub tcp_only_v6: bool,
#[cfg(feature = "socket_options")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "socket_options")))]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub tcp_listen_backlog: Option<u32>,
}
#[non_exhaustive]
#[cfg_attr(
feature = "serde",
derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ListenerAddress {
Tcp(SocketAddr),
Path(PathBuf),
Abstract(String),
Inetd,
FromFdTcp(i32),
FromFdUnix(i32),
}
#[cfg(feature = "clap")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "clap")))]
mod claptools {
#![allow(rustdoc::broken_intra_doc_links)]
use clap::Parser;
#[derive(Parser)]
pub struct ListenerAddressPositional {
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
doc = "Note that this platform does not support all the modes described above."
)]
#[cfg_attr(
not(feature = "user_facing_default"),
doc = "Note that some features may be disabled by compile-time settings."
)]
pub listen_address: crate::ListenerAddress,
#[allow(missing_docs)]
#[clap(flatten)]
pub listener_options: crate::UserOptions,
}
#[derive(Parser)]
pub struct ListenerAddressLFlag {
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
doc = "Note that this platform does not support all the modes described above."
)]
#[cfg_attr(
not(feature = "user_facing_default"),
doc = "Note that some features may be disabled by compile-time settings."
)]
#[clap(short = 'l', long = "listen-address")]
pub listen_address: Option<crate::ListenerAddress>,
#[allow(missing_docs)]
#[clap(flatten)]
pub listener_options: crate::UserOptions,
}
impl ListenerAddressPositional {
#[allow(clippy::missing_errors_doc)]
pub async fn bind(&self) -> std::io::Result<crate::Listener> {
crate::Listener::bind(
&self.listen_address,
&crate::SystemOptions::default(),
&self.listener_options,
)
.await
}
}
impl ListenerAddressLFlag {
pub async fn bind(&self) -> Option<std::io::Result<crate::Listener>> {
if let Some(addr) = self.listen_address.as_ref() {
Some(
crate::Listener::bind(
addr,
&crate::SystemOptions::default(),
&self.listener_options,
)
.await,
)
} else {
None
}
}
}
}
#[cfg(feature = "clap")]
pub use claptools::{ListenerAddressLFlag, ListenerAddressPositional};
const SD_LISTEN_FDS_START: i32 = 3;
impl FromStr for ListenerAddress {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with('/') || s.starts_with("./") {
Ok(ListenerAddress::Path(s.into()))
} else if let Some(x) = s.strip_prefix('@') {
Ok(ListenerAddress::Abstract(x.to_owned()))
} else if s.eq_ignore_ascii_case("inetd") || s.eq_ignore_ascii_case("stdio") || s == "-" {
Ok(ListenerAddress::Inetd)
} else if s.eq_ignore_ascii_case("sd-listen") || s.eq_ignore_ascii_case("sd_listen") {
Ok(ListenerAddress::FromFdTcp(SD_LISTEN_FDS_START))
} else if s.eq_ignore_ascii_case("sd-listen-unix")
|| s.eq_ignore_ascii_case("sd_listen_unix")
{
Ok(ListenerAddress::FromFdUnix(SD_LISTEN_FDS_START))
} else if let Ok(a) = s.parse() {
Ok(ListenerAddress::Tcp(a))
} else {
Err("Invalid tokio-listener address type")
}
}
}
impl Display for ListenerAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ListenerAddress::Tcp(a) => a.fmt(f),
ListenerAddress::Path(p) => {
if let Some(s) = p.to_str() {
if p.is_absolute() || s.starts_with("./") {
s.fmt(f)
} else {
write!(f, "./{s}")
}
} else if p.is_absolute() {
"/???".fmt(f)
} else {
"./???".fmt(f)
}
}
ListenerAddress::Abstract(p) => {
write!(f, "@{p}")
}
ListenerAddress::Inetd => "inetd".fmt(f),
ListenerAddress::FromFdTcp(fd) => {
if *fd == SD_LISTEN_FDS_START {
"sd-listen".fmt(f)
} else {
write!(f, "accept-tcp-from-fd:{fd}")
}
}
ListenerAddress::FromFdUnix(fd) => {
if *fd == SD_LISTEN_FDS_START {
"sd-listen-unix".fmt(f)
} else {
write!(f, "accept-unix-from-fd:{fd}")
}
}
}
}
}
#[non_exhaustive]
#[derive(Debug, Default, Clone, Copy)]
pub struct SystemOptions {
pub sleep_on_errors: bool,
pub nodelay: bool,
}
pub struct Listener {
i: ListenerImpl,
sleep_on_errors: bool,
timeout: Option<Pin<Box<Sleep>>>,
}
impl std::fmt::Debug for Listener {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.i {
ListenerImpl::Tcp { .. } => f.write_str("tokio_listener::Listener(tcp)"),
#[cfg(all(feature = "unix", unix))]
ListenerImpl::Unix { .. } => f.write_str("tokio_listener::Listener(unix)"),
#[cfg(feature = "inetd")]
ListenerImpl::Stdio(_) => f.write_str("tokio_listener::Listener(stdio)"),
}
}
}
#[cfg(feature = "sd_listen")]
#[allow(unused)]
fn check_env_for_fd(fdnum: i32) -> Option<()> {
let listen_pid = std::env::var("LISTEN_PID").ok()?;
let listen_pid: u32 = listen_pid.parse().ok()?;
let listen_fds = std::env::var("LISTEN_FDS").ok()?;
let listen_fds: i32 = listen_fds.parse().ok()?;
debug!("Parsed LISTEN_PID and LISTEN_FDS");
if listen_pid != std::process::id() {
error!(expected = %std::process::id(), actual=listen_pid, "Failed LISTEN_PID check");
return None;
}
if fdnum < SD_LISTEN_FDS_START || fdnum >= SD_LISTEN_FDS_START.checked_add(listen_fds)? {
error!(fdnum, listen_fds, "Failed LISTEN_FDS check");
return None;
}
Some(())
}
async fn listen_tcp(
a: &SocketAddr,
usr_opts: &UserOptions,
sys_opts: &SystemOptions,
) -> Result<ListenerImpl, std::io::Error> {
#[cfg(not(feature = "socket_options"))]
let s = TcpListener::bind(a).await?;
#[cfg(feature = "socket_options")]
let s =
if usr_opts.tcp_only_v6 || usr_opts.tcp_reuse_port || usr_opts.tcp_listen_backlog.is_some()
{
let s = socket2::Socket::new(
socket2::Domain::for_address(*a),
socket2::Type::STREAM,
None,
)?;
if usr_opts.tcp_only_v6 {
s.set_only_v6(true)?;
}
#[cfg(all(unix, not(any(target_os = "solaris", target_os = "illumos"))))]
if usr_opts.tcp_reuse_port {
s.set_reuse_port(true)?;
}
s.bind(&socket2::SockAddr::from(*a))?;
let backlog = usr_opts.tcp_listen_backlog.unwrap_or(1024);
let Ok(backlog): Result<c_int, _> = backlog.try_into() else {
return Err(std::io::Error::other("Invalid socket listen backlog value"));
};
s.listen(backlog)?;
s.set_nonblocking(true)?;
TcpListener::from_std(std::net::TcpListener::from(s))?
} else {
TcpListener::bind(a).await?
};
Ok(ListenerImpl::Tcp(ListenerImplTcp {
s,
nodelay: sys_opts.nodelay,
#[cfg(feature = "socket_options")]
keepalive: usr_opts
.tcp_keepalive
.as_ref()
.map(TcpKeepaliveParams::to_socket2),
#[cfg(feature = "socket_options")]
recv_buffer_size: usr_opts.recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size: usr_opts.send_buffer_size,
}))
}
#[cfg(all(unix, feature = "unix"))]
#[allow(clippy::similar_names)]
fn listen_path(usr_opts: &UserOptions, p: &PathBuf) -> Result<ListenerImpl, std::io::Error> {
#[cfg(feature = "unix_path_tools")]
#[allow(clippy::collapsible_if)]
if usr_opts.unix_listen_unlink {
if std::fs::remove_file(p).is_ok() {
debug!(file=?p, "removed UNIX socket before listening");
}
}
let i = ListenerImpl::Unix(ListenerImplUnix {
s: UnixListener::bind(p)?,
#[cfg(feature = "socket_options")]
recv_buffer_size: usr_opts.recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size: usr_opts.send_buffer_size,
});
#[cfg(feature = "unix_path_tools")]
{
use std::os::unix::fs::PermissionsExt;
if let Some(chmod) = usr_opts.unix_listen_chmod {
let mode = match chmod {
UnixChmodVariant::Owner => 0o006,
UnixChmodVariant::Group => 0o066,
UnixChmodVariant::Everybody => 0o666,
};
let perms = std::fs::Permissions::from_mode(mode);
std::fs::set_permissions(p, perms)?;
}
if (usr_opts.unix_listen_uid, usr_opts.unix_listen_gid) != (None, None) {
let uid = usr_opts.unix_listen_uid.map(Into::into);
let gid = usr_opts.unix_listen_gid.map(Into::into);
nix::unistd::chown(p, uid, gid)?;
}
}
Ok(i)
}
#[cfg(all(feature = "unix", any(target_os = "linux", target_os = "android")))]
fn listen_abstract(a: &String, usr_opts: &UserOptions) -> Result<ListenerImpl, std::io::Error> {
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt;
let a = std::os::unix::net::SocketAddr::from_abstract_name(a)?;
let s = std::os::unix::net::UnixListener::bind_addr(&a)?;
s.set_nonblocking(true)?;
Ok(ListenerImpl::Unix(ListenerImplUnix {
s: UnixListener::from_std(s)?,
#[cfg(feature = "socket_options")]
recv_buffer_size: usr_opts.recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size: usr_opts.send_buffer_size,
}))
}
#[cfg(all(feature = "sd_listen", unix))]
fn listen_from_fd_tcp(
usr_opts: &UserOptions,
fdnum: i32,
sys_opts: SystemOptions,
) -> Result<ListenerImpl, std::io::Error> {
use std::os::fd::FromRawFd;
if !usr_opts.sd_accept_ignore_environment && check_env_for_fd(fdnum).is_none() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed LISTEN_PID or LISTEN_FDS environment check for sd-listen mode",
));
}
let fd: RawFd = (fdnum).into();
let s = unsafe { std::net::TcpListener::from_raw_fd(fd) };
s.set_nonblocking(true)?;
Ok(ListenerImpl::Tcp(ListenerImplTcp {
s: TcpListener::from_std(s)?,
nodelay: sys_opts.nodelay,
#[cfg(feature = "socket_options")]
keepalive: usr_opts
.tcp_keepalive
.as_ref()
.map(TcpKeepaliveParams::to_socket2),
#[cfg(feature = "socket_options")]
recv_buffer_size: usr_opts.recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size: usr_opts.send_buffer_size,
}))
}
#[cfg(all(feature = "sd_listen", feature = "unix", unix))]
fn listen_from_fd_unix(usr_opts: &UserOptions, fdnum: i32) -> Result<ListenerImpl, std::io::Error> {
use std::os::fd::FromRawFd;
if !usr_opts.sd_accept_ignore_environment && check_env_for_fd(fdnum).is_none() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed LISTEN_PID or LISTEN_FDS environment check for sd-listen mode",
));
}
let fd: RawFd = (fdnum).into();
let s = unsafe { std::os::unix::net::UnixListener::from_raw_fd(fd) };
s.set_nonblocking(true)?;
Ok(ListenerImpl::Unix(ListenerImplUnix {
s: UnixListener::from_std(s)?,
#[cfg(feature = "socket_options")]
send_buffer_size: usr_opts.send_buffer_size,
#[cfg(feature = "socket_options")]
recv_buffer_size: usr_opts.recv_buffer_size,
}))
}
impl Listener {
#[allow(clippy::missing_errors_doc)]
pub async fn bind(
addr: &ListenerAddress,
sys_opts: &SystemOptions,
usr_opts: &UserOptions,
) -> std::io::Result<Self> {
let i: ListenerImpl = match addr {
ListenerAddress::Tcp(a) => listen_tcp(a, usr_opts, sys_opts).await?,
#[cfg(all(unix, feature = "unix"))]
ListenerAddress::Path(p) => listen_path(usr_opts, p)?,
#[cfg(all(feature = "unix", any(target_os = "linux", target_os = "android")))]
ListenerAddress::Abstract(a) => listen_abstract(a, usr_opts)?,
#[cfg(feature = "inetd")]
ListenerAddress::Inetd => {
let (tx, rx) = channel();
ListenerImpl::Stdio(StdioListener {
rx: rx.fuse(),
token: Some(tx),
})
}
#[cfg(all(feature = "sd_listen", unix))]
ListenerAddress::FromFdTcp(fdnum) => listen_from_fd_tcp(usr_opts, *fdnum, *sys_opts)?,
#[cfg(all(feature = "sd_listen", feature = "unix", unix))]
ListenerAddress::FromFdUnix(fdnum) => listen_from_fd_unix(usr_opts, *fdnum)?,
#[allow(unreachable_patterns)]
_ => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"This tokio-listener mode is not supported on this platform or is disabled during compilation",
));
}
};
Ok(Listener {
i,
sleep_on_errors: sys_opts.sleep_on_errors,
timeout: None,
})
}
}
#[cfg(feature = "inetd")]
struct StdioListener {
rx: Fuse<Receiver<()>>,
token: Option<Sender<()>>,
}
#[cfg(feature = "inetd")]
impl StdioListener {
fn poll_accept(
&mut self,
cx: &mut Context<'_>,
) -> Poll<std::io::Result<(Connection, SomeSocketAddr)>> {
match self.token.take() {
Some(tx) => {
debug!(r#type = "stdio", "incoming connection");
Poll::Ready(Ok((
Connection(ConnectionImpl::Stdio(
tokio::io::stdin(),
tokio::io::stdout(),
Some(tx),
)),
SomeSocketAddr::Stdio,
)))
}
None => match Pin::new(&mut self.rx).poll(cx) {
Poll::Ready(..) => {
trace!("finished waiting for liberation of stdout to stop listening loop");
Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Other,
"stdin/stdout pseudosocket is already used",
)))
}
Poll::Pending => Poll::Pending,
},
}
}
}
#[cfg(feature = "socket_options")]
fn apply_tcp_keepalive_opts(
c: &TcpStream,
keepalive: &Option<socket2::TcpKeepalive>,
) -> std::io::Result<()> {
let sock_ref = socket2::SockRef::from(&c);
if let Some(ka) = keepalive {
sock_ref.set_tcp_keepalive(ka)?;
}
Ok(())
}
#[cfg(all(feature = "socket_options", unix))]
fn apply_socket_buf_opts<T: std::os::fd::AsFd>(
c: &T,
recv_buffer_size: &Option<usize>,
send_buffer_size: &Option<usize>,
) -> std::io::Result<()> {
let sock_ref = socket2::SockRef::from(&c);
if let Some(n) = recv_buffer_size {
sock_ref.set_recv_buffer_size(*n)?;
}
if let Some(n) = send_buffer_size {
sock_ref.set_send_buffer_size(*n)?;
}
Ok(())
}
#[cfg(all(feature = "socket_options", not(unix)))]
fn apply_socket_buf_opts(
c: &TcpStream,
recv_buffer_size: &Option<usize>,
send_buffer_size: &Option<usize>,
) -> std::io::Result<()> {
let sock_ref = socket2::SockRef::from(&c);
if let Some(n) = recv_buffer_size {
sock_ref.set_recv_buffer_size(*n)?;
}
if let Some(n) = send_buffer_size {
sock_ref.set_send_buffer_size(*n)?;
}
Ok(())
}
struct ListenerImplTcp {
s: TcpListener,
nodelay: bool,
#[cfg(feature = "socket_options")]
keepalive: Option<socket2::TcpKeepalive>,
#[cfg(feature = "socket_options")]
recv_buffer_size: Option<usize>,
#[cfg(feature = "socket_options")]
send_buffer_size: Option<usize>,
}
#[cfg(all(feature = "unix", unix))]
struct ListenerImplUnix {
s: UnixListener,
#[cfg(feature = "socket_options")]
recv_buffer_size: Option<usize>,
#[cfg(feature = "socket_options")]
send_buffer_size: Option<usize>,
}
enum ListenerImpl {
Tcp(ListenerImplTcp),
#[cfg(all(feature = "unix", unix))]
Unix(ListenerImplUnix),
#[cfg(feature = "inetd")]
Stdio(StdioListener),
}
impl ListenerImplTcp {
fn poll_accept(
&mut self,
cx: &mut Context<'_>,
) -> Poll<std::io::Result<(Connection, SomeSocketAddr)>> {
let ListenerImplTcp {
s,
nodelay,
#[cfg(feature = "socket_options")]
keepalive,
#[cfg(feature = "socket_options")]
recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size,
} = self;
match s.poll_accept(cx) {
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Ready(Ok((c, a))) => {
debug!(fromaddr=%a, r#type="tcp", "incoming connection");
if *nodelay {
c.set_nodelay(true)?;
}
#[cfg(feature = "socket_options")]
{
apply_tcp_keepalive_opts(&c, keepalive)?;
apply_socket_buf_opts(&c, recv_buffer_size, send_buffer_size)?;
}
Poll::Ready(Ok((
Connection(ConnectionImpl::Tcp(c)),
SomeSocketAddr::Tcp(a),
)))
}
Poll::Pending => Poll::Pending,
}
}
}
#[cfg(all(feature = "unix", unix))]
impl ListenerImplUnix {
fn poll_accept(
&mut self,
cx: &mut Context<'_>,
) -> Poll<std::io::Result<(Connection, SomeSocketAddr)>> {
let ListenerImplUnix {
s,
#[cfg(feature = "socket_options")]
recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size,
} = self;
match s.poll_accept(cx) {
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Ready(Ok((c, a))) => {
debug!(r#type = "unix", "incoming connection");
#[cfg(feature = "socket_options")]
{
apply_socket_buf_opts(&c, recv_buffer_size, send_buffer_size)?;
}
Poll::Ready(Ok((
Connection(ConnectionImpl::Unix(c)),
SomeSocketAddr::Unix(a),
)))
}
Poll::Pending => Poll::Pending,
}
}
}
#[allow(clippy::missing_errors_doc)]
#[allow(missing_docs)]
impl Listener {
pub fn try_borrow_tcp_listener(&self) -> Option<&TcpListener> {
if let ListenerImpl::Tcp(ListenerImplTcp { ref s, .. }) = self.i {
Some(s)
} else {
None
}
}
#[cfg(all(feature = "unix", unix))]
#[cfg_attr(docsrs_alt, doc(cfg(all(feature = "unix", unix))))]
pub fn try_borrow_unix_listener(&self) -> Option<&UnixListener> {
if let ListenerImpl::Unix(ListenerImplUnix { s: ref x, .. }) = self.i {
Some(x)
} else {
None
}
}
pub fn try_into_tcp_listener(self) -> Result<TcpListener, Self> {
if let ListenerImpl::Tcp(ListenerImplTcp { s, .. }) = self.i {
Ok(s)
} else {
Err(self)
}
}
#[cfg(all(feature = "unix", unix))]
#[cfg_attr(docsrs_alt, doc(cfg(all(feature = "unix", unix))))]
pub fn try_into_unix_listener(self) -> Result<UnixListener, Self> {
if let ListenerImpl::Unix(ListenerImplUnix { s, .. }) = self.i {
Ok(s)
} else {
Err(self)
}
}
#[allow(unreachable_code)]
pub fn no_more_connections(&self) -> bool {
#[cfg(feature = "inetd")]
return if let ListenerImpl::Stdio(ref x) = self.i {
x.token.is_none()
} else {
false
};
false
}
pub fn poll_accept(
&mut self,
cx: &mut Context<'_>,
) -> Poll<std::io::Result<(Connection, SomeSocketAddr)>> {
loop {
if let Some(ref mut to) = self.timeout {
ready!(Pin::new(to).poll(cx));
}
self.timeout = None;
let ret = match &mut self.i {
ListenerImpl::Tcp(ti) => ti.poll_accept(cx),
#[cfg(all(feature = "unix", unix))]
ListenerImpl::Unix(ui) => ui.poll_accept(cx),
#[cfg(feature = "inetd")]
ListenerImpl::Stdio(x) => return x.poll_accept(cx),
};
let e: std::io::Error = match ret {
Poll::Ready(Err(e)) => e,
Poll::Ready(Ok(x)) => return Poll::Ready(Ok(x)),
Poll::Pending => return Poll::Pending,
};
if is_connection_error(&e) {
info!(action = "retry", "failed_accept");
continue;
}
if self.sleep_on_errors {
info!(action = "sleep_retry", "failed_accept");
self.timeout = Some(Box::pin(tokio::time::sleep(Duration::from_secs(1))));
} else {
info!(action = "error", "failed_accept");
return Poll::Ready(Err(e));
}
}
}
pub async fn accept(&mut self) -> std::io::Result<(Connection, SomeSocketAddr)> {
std::future::poll_fn(|cx| self.poll_accept(cx)).await
}
}
impl Stream for Listener {
type Item = std::io::Result<Connection>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.poll_accept(cx) {
Poll::Ready(Ok((connection, _))) => Poll::Ready(Some(Ok(connection))),
Poll::Ready(Err(err)) => Poll::Ready(Some(Err(err))),
Poll::Pending => Poll::Pending,
}
}
}
fn is_connection_error(e: &std::io::Error) -> bool {
matches!(
e.kind(),
std::io::ErrorKind::ConnectionRefused
| std::io::ErrorKind::ConnectionAborted
| std::io::ErrorKind::ConnectionReset
)
}
#[pin_project]
pub struct Connection(#[pin] ConnectionImpl);
impl std::fmt::Debug for Connection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
ConnectionImpl::Tcp(_) => f.write_str("Connection(tcp)"),
#[cfg(all(feature = "unix", unix))]
ConnectionImpl::Unix(_) => f.write_str("Connection(unix)"),
#[cfg(feature = "inetd")]
ConnectionImpl::Stdio(_, _, _) => f.write_str("Connection(stdio)"),
}
}
}
#[derive(Debug)]
#[pin_project(project = ConnectionImplProj)]
enum ConnectionImpl {
Tcp(#[pin] TcpStream),
#[cfg(all(feature = "unix", unix))]
Unix(#[pin] UnixStream),
#[cfg(feature = "inetd")]
Stdio(
#[pin] tokio::io::Stdin,
#[pin] tokio::io::Stdout,
Option<Sender<()>>,
),
}
#[allow(missing_docs)]
#[allow(clippy::missing_errors_doc)]
impl Connection {
pub fn try_into_tcp(self) -> Result<TcpStream, Self> {
if let ConnectionImpl::Tcp(s) = self.0 {
Ok(s)
} else {
Err(self)
}
}
#[cfg(all(feature = "unix", unix))]
#[cfg_attr(docsrs_alt, doc(cfg(all(feature = "unix", unix))))]
pub fn try_into_unix(self) -> Result<UnixStream, Self> {
if let ConnectionImpl::Unix(s) = self.0 {
Ok(s)
} else {
Err(self)
}
}
#[cfg(feature = "inetd")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "inetd")))]
pub fn try_into_stdio(self) -> Result<(Stdin, Stdout, Option<Sender<()>>), Self> {
if let ConnectionImpl::Stdio(i, o, f) = self.0 {
Ok((i, o, f))
} else {
Err(self)
}
}
pub fn try_borrow_tcp(&self) -> Option<&TcpStream> {
if let ConnectionImpl::Tcp(ref s) = self.0 {
Some(s)
} else {
None
}
}
#[cfg(all(feature = "unix", unix))]
#[cfg_attr(docsrs_alt, doc(cfg(all(feature = "unix", unix))))]
pub fn try_borrow_unix(&self) -> Option<&UnixStream> {
if let ConnectionImpl::Unix(ref s) = self.0 {
Some(s)
} else {
None
}
}
#[cfg(feature = "inetd")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "inetd")))]
pub fn try_borrow_stdio(&self) -> Option<(&Stdin, &Stdout)> {
if let ConnectionImpl::Stdio(ref i, ref o, ..) = self.0 {
Some((i, o))
} else {
None
}
}
}
impl From<TcpStream> for Connection {
fn from(s: TcpStream) -> Self {
Connection(ConnectionImpl::Tcp(s))
}
}
#[cfg(all(feature = "unix", unix))]
#[cfg_attr(docsrs_alt, doc(cfg(all(feature = "unix", unix))))]
impl From<UnixStream> for Connection {
fn from(s: UnixStream) -> Self {
Connection(ConnectionImpl::Unix(s))
}
}
#[cfg(feature = "inetd")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "inetd")))]
impl From<(Stdin, Stdout, Option<Sender<()>>)> for Connection {
fn from(s: (Stdin, Stdout, Option<Sender<()>>)) -> Self {
Connection(ConnectionImpl::Stdio(s.0, s.1, s.2))
}
}
impl AsyncRead for Connection {
#[inline]
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_read(cx, buf),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_read(cx, buf),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(s, _, _) => s.poll_read(cx, buf),
}
}
}
impl AsyncWrite for Connection {
#[inline]
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_write(cx, buf),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_write(cx, buf),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(_, s, _) => s.poll_write(cx, buf),
}
}
#[inline]
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_flush(cx),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_flush(cx),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(_, s, _) => s.poll_flush(cx),
}
}
#[inline]
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_shutdown(cx),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_shutdown(cx),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(_, s, tx) => match s.poll_shutdown(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(ret) => {
if let Some(tx) = tx.take() {
if tx.send(()).is_err() {
warn!("stdout wrapper for inetd mode failed to notify the listener to abort listening loop");
} else {
debug!("stdout finished in inetd mode. Aborting the listening loop.");
}
}
Poll::Ready(ret)
}
},
}
}
#[inline]
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> Poll<Result<usize, std::io::Error>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_write_vectored(cx, bufs),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_write_vectored(cx, bufs),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(_, s, _) => s.poll_write_vectored(cx, bufs),
}
}
#[inline]
fn is_write_vectored(&self) -> bool {
match &self.0 {
ConnectionImpl::Tcp(s) => s.is_write_vectored(),
#[cfg(all(feature = "unix", unix))]
ConnectionImpl::Unix(s) => s.is_write_vectored(),
#[cfg(feature = "inetd")]
ConnectionImpl::Stdio(_, s, _) => s.is_write_vectored(),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum SomeSocketAddr {
Tcp(SocketAddr),
#[cfg(all(feature = "unix", unix))]
#[cfg_attr(docsrs_alt, doc(cfg(all(feature = "unix", unix))))]
Unix(tokio::net::unix::SocketAddr),
#[cfg(feature = "inetd")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "inetd")))]
Stdio,
}
impl Display for SomeSocketAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SomeSocketAddr::Tcp(x) => x.fmt(f),
#[cfg(all(feature = "unix", unix))]
SomeSocketAddr::Unix(_x) => "unix".fmt(f),
#[cfg(feature = "inetd")]
SomeSocketAddr::Stdio => "stdio".fmt(f),
}
}
}
impl SomeSocketAddr {
#[must_use]
pub fn clonable(self) -> SomeSocketAddrClonable {
match self {
SomeSocketAddr::Tcp(x) => SomeSocketAddrClonable::Tcp(x),
#[cfg(all(feature = "unix", unix))]
SomeSocketAddr::Unix(x) => SomeSocketAddrClonable::Unix(Arc::new(x)),
#[cfg(feature = "inetd")]
SomeSocketAddr::Stdio => SomeSocketAddrClonable::Stdio,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum SomeSocketAddrClonable {
Tcp(SocketAddr),
#[cfg(all(feature = "unix", unix))]
#[cfg_attr(docsrs_alt, doc(cfg(all(feature = "unix", unix))))]
Unix(Arc<tokio::net::unix::SocketAddr>),
#[cfg(feature = "inetd")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "inetd")))]
Stdio,
}
impl Display for SomeSocketAddrClonable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SomeSocketAddrClonable::Tcp(x) => x.fmt(f),
#[cfg(all(feature = "unix", unix))]
SomeSocketAddrClonable::Unix(x) => write!(f, "unix:{x:?}"),
#[cfg(feature = "inetd")]
SomeSocketAddrClonable::Stdio => "stdio".fmt(f),
}
}
}
impl From<SomeSocketAddr> for SomeSocketAddrClonable {
fn from(value: SomeSocketAddr) -> Self {
value.clonable()
}
}
#[cfg(feature = "hyper014")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "hyper014")))]
mod hyper014 {
use std::{
pin::Pin,
task::{self, Poll},
};
use hyper014::server::accept::Accept;
use crate::Listener;
impl Accept for Listener {
type Conn = crate::Connection;
type Error = std::io::Error;
fn poll_accept(
self: std::pin::Pin<&mut Self>,
cx: &mut task::Context<'_>,
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
let nmc = self.no_more_connections();
match crate::Listener::poll_accept(Pin::<_>::into_inner(self), cx) {
Poll::Ready(Ok((s, _a))) => Poll::Ready(Some(Ok(s))),
Poll::Ready(Err(e)) => {
if nmc {
return Poll::Ready(None);
}
Poll::Ready(Some(Err(e)))
}
Poll::Pending => Poll::Pending,
}
}
}
}
#[cfg(feature = "axum07")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "axum07")))]
pub mod axum07;
#[cfg(feature = "tonic010")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "tonic010")))]
mod tonic010 {
use tonic_010::transport::server::{Connected, TcpConnectInfo};
#[cfg(all(feature = "unix", unix))]
use tonic_010::transport::server::UdsConnectInfo;
use crate::Connection;
#[derive(Clone)]
pub enum ListenerConnectInfo {
Tcp(TcpConnectInfo),
#[cfg(all(feature = "unix", unix))]
#[cfg_attr(docsrs_alt, doc(cfg(all(feature = "unix", unix))))]
Unix(UdsConnectInfo),
#[cfg(feature = "inetd")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "inetd")))]
Stdio,
Other,
}
impl Connected for Connection {
type ConnectInfo = ListenerConnectInfo;
fn connect_info(&self) -> Self::ConnectInfo {
if let Some(tcp_stream) = self.try_borrow_tcp() {
return ListenerConnectInfo::Tcp(tcp_stream.connect_info());
}
#[cfg(all(feature = "unix", unix))]
if let Some(unix_stream) = self.try_borrow_unix() {
return ListenerConnectInfo::Unix(unix_stream.connect_info());
}
#[cfg(feature = "inetd")]
if self.try_borrow_stdio().is_some() {
return ListenerConnectInfo::Stdio;
}
ListenerConnectInfo::Other
}
}
}
#[cfg(feature = "tonic011")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "tonic011")))]
mod tonic011 {
use tonic::transport::server::{Connected, TcpConnectInfo};
#[cfg(all(feature = "unix", unix))]
use tonic::transport::server::UdsConnectInfo;
use crate::Connection;
#[derive(Clone)]
pub enum ListenerConnectInfo {
Tcp(TcpConnectInfo),
#[cfg(all(feature = "unix", unix))]
#[cfg_attr(docsrs_alt, doc(cfg(all(feature = "unix", unix))))]
Unix(UdsConnectInfo),
#[cfg(feature = "inetd")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "inetd")))]
Stdio,
Other,
}
impl Connected for Connection {
type ConnectInfo = ListenerConnectInfo;
fn connect_info(&self) -> Self::ConnectInfo {
if let Some(tcp_stream) = self.try_borrow_tcp() {
return ListenerConnectInfo::Tcp(tcp_stream.connect_info());
}
#[cfg(all(feature = "unix", unix))]
if let Some(unix_stream) = self.try_borrow_unix() {
return ListenerConnectInfo::Unix(unix_stream.connect_info());
}
#[cfg(feature = "inetd")]
if self.try_borrow_stdio().is_some() {
return ListenerConnectInfo::Stdio;
}
ListenerConnectInfo::Other
}
}
}
#[cfg(feature = "tokio-util")]
#[cfg_attr(docsrs_alt, doc(cfg(feature = "tokio-util")))]
mod tokioutil {
use crate::SomeSocketAddr;
impl tokio_util::net::Listener for crate::Listener {
type Io = crate::Connection;
type Addr = SomeSocketAddr;
fn poll_accept(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<(Self::Io, Self::Addr)>> {
self.poll_accept(cx)
}
fn local_addr(&self) -> std::io::Result<Self::Addr> {
match &self.i {
crate::ListenerImpl::Tcp(crate::ListenerImplTcp { s, .. }) => {
Ok(SomeSocketAddr::Tcp(s.local_addr()?))
}
#[cfg(all(feature = "unix", unix))]
crate::ListenerImpl::Unix(crate::ListenerImplUnix { s, .. }) => {
Ok(SomeSocketAddr::Unix(s.local_addr()?))
}
#[cfg(feature = "inetd")]
crate::ListenerImpl::Stdio(_) => Ok(SomeSocketAddr::Stdio),
}
}
}
}