//! Thin TCP socket layer. //! //! We don't use `std::net::TcpListener` because we need non-blocking accept //! integrated with smarm's epoll loop. So this drops down to libc: socket, //! bind, listen, accept4. All fds carry `O_NONBLOCK | O_CLOEXEC` so smarm's //! readiness primitives work as documented. use std::io; use std::net::SocketAddr; use std::os::fd::RawFd; // --------------------------------------------------------------------------- // OwnedFd — a tiny RAII wrapper that closes on drop. // --------------------------------------------------------------------------- // // Sent through smarm channels (which require `Send`) for zero-copy fd // handoff between listener and connection actor. `Send` is safe: fd values // are just integers, and ownership semantics — exactly-one closer — are // enforced by the type itself. #[derive(Debug)] pub struct OwnedFd { fd: RawFd, } impl OwnedFd { /// Wrap a raw fd. The wrapper now owns the fd and will close it on drop. pub fn from_raw(fd: RawFd) -> Self { Self { fd } } pub fn as_raw(&self) -> RawFd { self.fd } /// Release ownership without closing. The caller must close the fd. pub fn into_raw(self) -> RawFd { let fd = self.fd; std::mem::forget(self); fd } } impl Drop for OwnedFd { fn drop(&mut self) { if self.fd >= 0 { unsafe { libc::close(self.fd); } } } } // fd handoff: SAFETY: RawFd is an integer; sending one across threads // transfers ownership in the same way moving an i32 would. The recipient // becomes the unique closer. unsafe impl Send for OwnedFd {} // SAFETY: shared references only expose `as_raw(&self)` — reading an // integer. No interior mutability; `into_raw` takes `self` by value and is // unreachable through a shared reference. Needed so a supervisor // `ChildSpec` factory (an `Fn` closure, hence shared across restarts) can // own its listener fd via `Arc`. unsafe impl Sync for OwnedFd {} // --------------------------------------------------------------------------- // bind_and_listen // --------------------------------------------------------------------------- const LISTEN_BACKLOG: i32 = 1024; /// Create a non-blocking, SO_REUSEADDR TCP listener bound to `addr`. The /// returned fd is ready for `accept4` calls; the caller registers it with /// smarm via `wait_readable` between accepts. pub fn bind_and_listen(addr: SocketAddr) -> io::Result { let family = match addr { SocketAddr::V4(_) => libc::AF_INET, SocketAddr::V6(_) => libc::AF_INET6, }; let fd = unsafe { libc::socket( family, libc::SOCK_STREAM | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC, 0, ) }; if fd < 0 { return Err(io::Error::last_os_error()); } let owned = OwnedFd::from_raw(fd); // SO_REUSEADDR — standard for servers; avoids TIME_WAIT bind failures // on restart. let opt: libc::c_int = 1; let r = unsafe { libc::setsockopt( fd, libc::SOL_SOCKET, libc::SO_REUSEADDR, &opt as *const _ as *const libc::c_void, std::mem::size_of_val(&opt) as libc::socklen_t, ) }; if r < 0 { return Err(io::Error::last_os_error()); } // bind(). match addr { SocketAddr::V4(a) => { let sa = libc::sockaddr_in { sin_family: libc::AF_INET as u16, sin_port: a.port().to_be(), sin_addr: libc::in_addr { s_addr: u32::from_ne_bytes(a.ip().octets()) }, sin_zero: [0; 8], }; let r = unsafe { libc::bind( fd, &sa as *const _ as *const libc::sockaddr, std::mem::size_of::() as libc::socklen_t, ) }; if r < 0 { return Err(io::Error::last_os_error()); } } SocketAddr::V6(a) => { let sa = libc::sockaddr_in6 { sin6_family: libc::AF_INET6 as u16, sin6_port: a.port().to_be(), sin6_flowinfo: a.flowinfo(), sin6_addr: libc::in6_addr { s6_addr: a.ip().octets() }, sin6_scope_id: a.scope_id(), }; let r = unsafe { libc::bind( fd, &sa as *const _ as *const libc::sockaddr, std::mem::size_of::() as libc::socklen_t, ) }; if r < 0 { return Err(io::Error::last_os_error()); } } } // listen(). let r = unsafe { libc::listen(fd, LISTEN_BACKLOG) }; if r < 0 { return Err(io::Error::last_os_error()); } Ok(owned) } // --------------------------------------------------------------------------- // accept_nonblocking // --------------------------------------------------------------------------- /// One non-blocking `accept4`. Returns the new fd on success, /// `Err(WouldBlock)` if no connection is pending (caller should park on /// `wait_readable(listener)` and retry), or other errors directly. pub fn accept_nonblocking(listener: RawFd) -> io::Result { let mut addr: libc::sockaddr_storage = unsafe { std::mem::zeroed() }; let mut len: libc::socklen_t = std::mem::size_of::() as libc::socklen_t; let fd = unsafe { libc::accept4( listener, &mut addr as *mut _ as *mut libc::sockaddr, &mut len, libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC, ) }; if fd < 0 { return Err(io::Error::last_os_error()); } Ok(OwnedFd::from_raw(fd)) }