use crate::{
cleanup_unix_path_socket,
errors::OpenSocketError,
SocketAppOptions,
SocketAddr,
SocketUserOptions,
sys,
util::*,
};
use socket2::Socket;
use std::{
fs,
path::Path,
};
#[cfg(doc)]
use crate::convert::AnyStdSocket;
#[cfg(all(doc, feature = "tokio"))]
use crate::convert::AnyTokioListener;
#[cfg_attr(feature = "tokio", doc = r#"
* Converted to [`AnyTokioListener`]. This accepts connections on a TCP or Unix-domain listening socket using [`tokio`] non-blocking I/O."#)]
pub fn open(
address: &SocketAddr,
app_options: &SocketAppOptions,
user_options: &SocketUserOptions,
) -> Result<Socket, OpenSocketError> {
let orig_address = address;
let open_new = |address: socket2::SockAddr| -> Result<Socket, OpenSocketError> {
let unix_socket_path: Option<&Path> = match orig_address {
SocketAddr::Unix { path } => Some(path),
_ => None,
};
#[cfg(unix)]
crate::unix_security::prepare(user_options, unix_socket_path)?;
let listen_backlog: Option<_> = {
if app_options.listen && app_options.r#type == socket2::Type::STREAM {
Some(
user_options.listen_socket_backlog
.unwrap_or(SocketUserOptions::DEFAULT_LISTEN_SOCKET_BACKLOG)
)
}
else {
check_inapplicable(user_options.listen_socket_backlog, "listen_socket_backlog")?;
None
}
};
let mut socket: socket2::Socket =
Socket::new(address.domain(), app_options.r#type, app_options.protocol)
.map_err(|error| OpenSocketError::CreateSocket { error })?;
if let Some(socket_path) = unix_socket_path {
if !user_options.unix_socket_no_unlink {
cleanup_unix_path_socket(socket_path)?;
}
if let Some(socket_parent_path) = socket_path.parent() {
fs::create_dir_all(socket_parent_path)
.map_err(|error| OpenSocketError::MkdirParents { error })?;
}
}
#[cfg(not(windows))]
if listen_backlog.is_some() && is_socket_probably_tcp(&socket, &address, app_options) {
socket.set_reuse_address(true)
.map_err(|error| OpenSocketError::SetSockOpt {
option: "SO_REUSEADDR",
error,
})?;
}
#[cfg(all(unix, not(any(target_os = "solaris", target_os = "illumos"))))]
if user_options.ip_socket_reuse_port {
socket.set_reuse_port(true)
.map_err(|error| OpenSocketError::SetSockOpt {
option: "SO_REUSEPORT",
error,
})?;
}
if user_options.ip_socket_v6_only {
socket.set_only_v6(true)
.map_err(|error| OpenSocketError::SetSockOpt {
option: "IPV6_V6ONLY",
error,
})?;
}
if let Some(before_bind) = &app_options.before_bind {
before_bind(&mut socket)
.map_err(OpenSocketError::BeforeBind)?;
}
socket.bind(&address)
.map_err(|error| OpenSocketError::Bind { error })?;
#[cfg(unix)]
crate::unix_security::apply(user_options, &socket, unix_socket_path)?;
if let Some(listen_backlog) = listen_backlog {
socket.listen(listen_backlog)
.map_err(|error| OpenSocketError::Listen { error })?;
}
Ok(socket)
};
let inherit = |socket: sys::RawSocket| -> Result<Socket, OpenSocketError> {
sys::startup_socket_api();
#[cfg(unix)] {
check_inapplicable(user_options.unix_socket_permissions.as_ref(), "unix_socket_permissions")?;
check_inapplicable(user_options.unix_socket_owner.as_ref(), "unix_socket_owner")?;
check_inapplicable(user_options.unix_socket_group.as_ref(), "unix_socket_group")?;
}
#[cfg(all(unix, not(any(target_os = "solaris", target_os = "illumos"))))]
check_inapplicable_bool(user_options.ip_socket_reuse_port, "ip_socket_reuse_port")?;
check_inapplicable_bool(user_options.ip_socket_v6_only, "ip_socket_v6_only")?;
check_inapplicable(user_options.listen_socket_backlog, "listen_socket_backlog")?;
let socket: sys::BorrowedSocket<'_> = unsafe {
sys::BorrowedSocket::borrow_raw(socket)
};
let socket: sys::OwnedSocket =
socket.try_clone_to_owned()
.map_err(|error| OpenSocketError::DupInherited { error })?;
let socket: Socket = Socket::from(socket);
let actual_type: socket2::Type =
socket.r#type()
.map_err(|error| OpenSocketError::CheckInheritedSocket { error })?;
if actual_type != app_options.r#type {
return Err(OpenSocketError::InheritWrongType {
expected: app_options.r#type,
actual: actual_type,
});
}
#[cfg(any(
target_os = "aix",
target_os = "android",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "linux",
))]
if actual_type == socket2::Type::STREAM {
if let Ok(actual_listen) = socket.is_listener() {
if app_options.listen != actual_listen {
return Err(match app_options.listen {
true => OpenSocketError::InheritedIsNotListening,
false => OpenSocketError::InheritedIsListening,
});
}}}
Ok(socket)
};
let socket: Socket = match address {
SocketAddr::Ip { addr, port } => {
let port: u16 =
(*port)
.or(app_options.default_port)
.ok_or(OpenSocketError::PortRequired)?;
let addr = std::net::SocketAddr::new(*addr, port);
open_new(addr.into())?
}
SocketAddr::Unix { path } => {
let address =
socket2::SockAddr::unix(path)
.map_err(|error| OpenSocketError::InvalidUnixPath { error })?;
open_new(address)?
},
SocketAddr::Inherit { socket } => inherit(*socket)?,
SocketAddr::InheritStdin {} => {
let socket: sys::RawSocket = sys::get_stdin_as_socket().map_err(|error| -> OpenSocketError {
match error {
#[cfg(windows)]
error @ std::io::Error { .. } => OpenSocketError::WindowsGetStdin { error },
}
})?;
inherit(socket)?
},
#[cfg(not(windows))]
SocketAddr::SystemdNumeric { socket } => {
if
*socket >= sys::SD_LISTEN_FDS_START ||
sys::SD_LISTEN_FDS_END.is_some_and(|sd_listen_fds_end| *socket <= sd_listen_fds_end)
{
inherit(*socket)?
}
else {
return Err(OpenSocketError::InvalidSystemdFd)
}
},
};
Ok(socket)
}