mirror of
https://github.com/telemt/telemt.git
synced 2026-10-11 11:55:59 +03:00
Direct-since-epoch
This commit is contained in:
@@ -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(¤t) != 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(®ular, b"preserve").unwrap();
|
||||
symlink(®ular, &link).unwrap();
|
||||
|
||||
assert!(remove_stale_unix_socket(®ular).is_err());
|
||||
assert!(remove_stale_unix_socket(&link).is_err());
|
||||
assert_eq!(std::fs::read(®ular).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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user