Direct-since-epoch

This commit is contained in:
Alexey
2026-09-15 20:37:41 +03:00
parent 5ec9f85530
commit 55f3d19ee0
9 changed files with 483 additions and 73 deletions
+126 -5
View File
@@ -1,5 +1,13 @@
use std::error::Error;
#[cfg(unix)]
use std::io::{Error as IoError, ErrorKind};
use std::net::{IpAddr, SocketAddr};
#[cfg(unix)]
use std::os::unix::fs::{FileTypeExt, MetadataExt};
#[cfg(unix)]
use std::os::unix::net::UnixStream as StdUnixStream;
#[cfg(unix)]
use std::path::Path;
use std::sync::Arc;
use socket2::Socket;
@@ -187,6 +195,61 @@ fn print_configured_links(
print_proxy_links(host, port, config);
}
#[cfg(unix)]
fn unix_path_identity(metadata: &std::fs::Metadata) -> (u64, u64) {
(metadata.dev(), metadata.ino())
}
#[cfg(unix)]
fn remove_stale_unix_socket(path: &Path) -> std::io::Result<()> {
let metadata = match std::fs::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(error),
};
if !metadata.file_type().is_socket() {
return Err(IoError::new(
ErrorKind::AlreadyExists,
format!(
"refusing to remove non-socket Unix listener path {}",
path.display()
),
));
}
match StdUnixStream::connect(path) {
Ok(_) => {
return Err(IoError::new(
ErrorKind::AddrInUse,
format!("Unix listener {} is already active", path.display()),
));
}
Err(error) if error.kind() == ErrorKind::ConnectionRefused => {}
Err(error) => return Err(error),
}
let current = std::fs::symlink_metadata(path)?;
if !current.file_type().is_socket()
|| unix_path_identity(&current) != unix_path_identity(&metadata)
{
return Err(IoError::new(
ErrorKind::AlreadyExists,
format!("Unix listener path {} changed during cleanup", path.display()),
));
}
std::fs::remove_file(path)
}
#[cfg(unix)]
fn verify_bound_unix_socket(path: &Path, expected: (u64, u64)) -> std::io::Result<()> {
let metadata = std::fs::symlink_metadata(path)?;
if metadata.file_type().is_socket() && unix_path_identity(&metadata) == expected {
return Ok(());
}
Err(IoError::new(
ErrorKind::AlreadyExists,
format!("Unix listener path {} was replaced", path.display()),
))
}
/// Binds every eligible configured listener or fails without a partial inventory.
pub(crate) async fn bind_listeners(
config: &Arc<ProxyConfig>,
@@ -217,27 +280,39 @@ pub(crate) async fn bind_listeners(
let mut unix_listener_out = None;
#[cfg(unix)]
if let Some(unix_path) = &config.server.listen_unix_sock {
let _ = tokio::fs::remove_file(unix_path).await;
let unix_path = Path::new(unix_path);
remove_stale_unix_socket(unix_path)?;
let unix_listener = UnixListener::bind(unix_path)?;
let socket_metadata = std::fs::symlink_metadata(unix_path)?;
if !socket_metadata.file_type().is_socket() {
return Err(IoError::new(
ErrorKind::AlreadyExists,
format!("Unix listener path {} was replaced", unix_path.display()),
)
.into());
}
let socket_identity = unix_path_identity(&socket_metadata);
if let Some(perm_str) = &config.server.listen_unix_sock_perm {
match u32::from_str_radix(perm_str.trim_start_matches('0'), 8) {
Ok(mode) => {
use std::os::unix::fs::PermissionsExt;
let permissions = std::fs::Permissions::from_mode(mode);
verify_bound_unix_socket(unix_path, socket_identity)?;
if let Err(error_value) = std::fs::set_permissions(unix_path, permissions) {
error!(
path = %unix_path,
path = %unix_path.display(),
permissions = %perm_str,
error = %error_value,
"Failed to set Unix socket permissions"
);
} else {
info!(path = %unix_path, permissions = %perm_str, "Listening on Unix socket");
verify_bound_unix_socket(unix_path, socket_identity)?;
info!(path = %unix_path.display(), permissions = %perm_str, "Listening on Unix socket");
}
}
Err(error_value) => {
warn!(
path = %unix_path,
path = %unix_path.display(),
permissions = %perm_str,
error = %error_value,
"Invalid Unix socket permissions; keeping umask-derived mode"
@@ -245,7 +320,7 @@ pub(crate) async fn bind_listeners(
}
}
} else {
info!(path = %unix_path, "Listening on Unix socket");
info!(path = %unix_path.display(), "Listening on Unix socket");
}
unix_listener_out = Some(unix_listener);
}
@@ -271,3 +346,49 @@ pub(crate) async fn bind_listeners(
unix_listener: unix_listener_out,
})
}
#[cfg(all(test, unix))]
mod tests {
use std::os::unix::fs::symlink;
use std::os::unix::net::UnixListener as StdUnixListener;
use super::*;
#[test]
fn unix_socket_cleanup_refuses_regular_file_and_symlink() {
let directory = tempfile::tempdir().unwrap();
let regular = directory.path().join("regular");
let link = directory.path().join("listener.sock");
std::fs::write(&regular, b"preserve").unwrap();
symlink(&regular, &link).unwrap();
assert!(remove_stale_unix_socket(&regular).is_err());
assert!(remove_stale_unix_socket(&link).is_err());
assert_eq!(std::fs::read(&regular).unwrap(), b"preserve");
assert!(std::fs::symlink_metadata(&link).unwrap().file_type().is_symlink());
}
#[test]
fn unix_socket_cleanup_removes_only_stale_socket() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("listener.sock");
let listener = StdUnixListener::bind(&path).unwrap();
drop(listener);
remove_stale_unix_socket(&path).unwrap();
assert!(!path.exists());
}
#[test]
fn unix_socket_cleanup_preserves_live_listener() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("listener.sock");
let _listener = StdUnixListener::bind(&path).unwrap();
let error = remove_stale_unix_socket(&path).unwrap_err();
assert_eq!(error.kind(), ErrorKind::AddrInUse);
assert!(path.exists());
}
}