Initial commit
This commit is contained in:
+165
@@ -0,0 +1,165 @@
|
||||
//! 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 {}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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<OwnedFd> {
|
||||
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::<libc::sockaddr_in>() 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::<libc::sockaddr_in6>() 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<OwnedFd> {
|
||||
let mut addr: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
|
||||
let mut len: libc::socklen_t = std::mem::size_of::<libc::sockaddr_storage>() 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))
|
||||
}
|
||||
Reference in New Issue
Block a user