//! Linux networking backend. use std::cell::Cell; use std::ffi::c_void; use std::future::Future; use std::io; use std::mem::MaybeUninit; use std::net::{ Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs, }; use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}; use std::pin::Pin; use std::thread; use std::time::Duration; thread_local! { // None = untested, Some(true) = io_uring works, Some(false) = use offload. // After the first successful IORING_OP_SEND, the clone in send() is skipped // for all subsequent calls on the same thread. static SEND_URING_SUPPORTED: Cell> = const { Cell::new(None) }; } use crate::op::completion::completion_for_current_thread; use crate::op::net::{AcceptedSocket, NetOp, ReceivedDatagram}; use crate::platform::linux_x86_64::runtime::with_current_driver; use crate::platform::linux_x86_64::uring::{ IORING_OP_ACCEPT, IORING_OP_BIND, IORING_OP_CLOSE, IORING_OP_CONNECT, IORING_OP_LISTEN, IORING_OP_RECV, IORING_OP_RECVMSG, IORING_OP_SEND, IORING_OP_SENDMSG, IORING_OP_SHUTDOWN, IORING_OP_SOCKET, IoUringCqe, IoUringSqe, }; const DEFAULT_LISTENER_BACKLOG: i32 = 1024; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ExecutionPath { IoUring, Offload, } pub fn execution_path(op: &NetOp) -> ExecutionPath { match op { NetOp::Socket { .. } | NetOp::Connect { .. } | NetOp::Bind { .. } | NetOp::Listen { .. } | NetOp::Accept { .. } | NetOp::Send { .. } | NetOp::Recv { .. } | NetOp::Shutdown { .. } | NetOp::Close { .. } => ExecutionPath::IoUring, NetOp::SendTo { .. } | NetOp::RecvFrom { .. } => ExecutionPath::IoUring, } } pub async fn resolve_addrs(addr: A) -> io::Result> where A: ToSocketAddrs + Send + 'static, { offload(move || { let addrs = addr.to_socket_addrs()?.collect::>(); if addrs.is_empty() { Err(io::Error::new( io::ErrorKind::InvalidInput, "address resolved to no socket addresses", )) } else { Ok(addrs) } }) .await } pub async fn socket(op: NetOp) -> io::Result { let NetOp::Socket { domain, socket_type, protocol, flags, } = op else { unreachable!("socket backend called with non-socket op"); }; match submit_uring::( move |sqe| { sqe.opcode = IORING_OP_SOCKET; sqe.fd = domain; sqe.off = socket_type as u64; sqe.len = protocol as u32; sqe.op_flags = flags; }, move |cqe| cqe_to_result(cqe).map(|fd| unsafe { OwnedFd::from_raw_fd(fd as RawFd) }), ) .await { Err(error) if should_fallback_to_offload(&error) => { offload(move || socket_sync(domain, socket_type, protocol, flags)).await } result => result, } } pub async fn connect(op: NetOp) -> io::Result<()> { let NetOp::Connect { fd, addr } = op else { unreachable!("connect backend called with non-connect op"); }; let raw_addr = RawSocketAddr::from_socket_addr(addr); let fallback_addr = raw_addr; let addr_ptr = raw_addr.as_ptr(); let addr_len = raw_addr.len(); match submit_uring::<(), _>( move |sqe| { sqe.opcode = IORING_OP_CONNECT; sqe.fd = fd; sqe.addr = addr_ptr as u64; sqe.off = addr_len as u64; }, move |cqe| { let _raw_addr = raw_addr; cqe_to_result(cqe).map(|_| ()) }, ) .await { Err(error) if should_fallback_to_offload(&error) => { offload(move || connect_sync(fd, fallback_addr)).await } result => result, } } pub async fn bind(op: NetOp) -> io::Result<()> { let NetOp::Bind { fd, addr } = op else { unreachable!("bind backend called with non-bind op"); }; let raw_addr = RawSocketAddr::from_socket_addr(addr); let fallback_addr = raw_addr; let addr_ptr = raw_addr.as_ptr(); let addr_len = raw_addr.len(); match submit_uring::<(), _>( move |sqe| { sqe.opcode = IORING_OP_BIND; sqe.fd = fd; sqe.addr = addr_ptr as u64; sqe.off = addr_len as u64; }, move |cqe| { let _raw_addr = raw_addr; cqe_to_result(cqe).map(|_| ()) }, ) .await { Err(error) if should_fallback_to_offload(&error) => { offload(move || bind_sync(fd, fallback_addr)).await } result => result, } } pub async fn listen(op: NetOp) -> io::Result<()> { let NetOp::Listen { fd, backlog } = op else { unreachable!("listen backend called with non-listen op"); }; match submit_uring::<(), _>( move |sqe| { sqe.opcode = IORING_OP_LISTEN; sqe.fd = fd; sqe.len = backlog as u32; }, move |cqe| cqe_to_result(cqe).map(|_| ()), ) .await { Err(error) if should_fallback_to_offload(&error) => { offload(move || listen_sync(fd, backlog)).await } result => result, } } pub async fn accept(op: NetOp) -> io::Result { let NetOp::Accept { fd } = op else { unreachable!("accept backend called with non-accept op"); }; let mut storage = Box::new(MaybeUninit::::zeroed()); let mut addr_len = Box::new(std::mem::size_of::() as libc::socklen_t); let storage_ptr = storage.as_mut_ptr(); let addr_len_ptr = addr_len.as_mut() as *mut libc::socklen_t; match submit_uring::( move |sqe| { sqe.opcode = IORING_OP_ACCEPT; sqe.fd = fd; sqe.addr = storage_ptr as u64; sqe.off = addr_len_ptr as u64; }, move |cqe| { let accepted_fd = cqe_to_result(cqe)? as RawFd; let storage = unsafe { storage.assume_init() }; let peer_addr = socket_addr_from_storage(&storage, *addr_len)?; Ok(AcceptedSocket { fd: accepted_fd, peer_addr, }) }, ) .await { Err(error) if should_fallback_to_offload(&error) => offload(move || accept_sync(fd)).await, result => result, } } pub async fn send(op: NetOp) -> io::Result { let NetOp::Send { fd, data, flags } = op else { unreachable!("send backend called with non-send op"); }; // Capability known: io_uring SEND is not supported — skip to offload directly. if SEND_URING_SUPPORTED.with(|c| c.get()) == Some(false) { return offload(move || send_sync(fd, data, flags)).await; } // Capability known: io_uring SEND works — submit without a fallback clone. if SEND_URING_SUPPORTED.with(|c| c.get()) == Some(true) { let data_ptr = data.as_ptr(); let data_len = data.len(); return submit_uring::( move |sqe| { sqe.opcode = IORING_OP_SEND; sqe.fd = fd; sqe.addr = data_ptr as u64; sqe.len = data_len as u32; sqe.op_flags = flags as u32; }, move |cqe| { let _data = data; cqe_to_result(cqe).map(|written| written as usize) }, ) .await; } // Capability unknown: probe with a one-time clone. Cache the result. let fallback_data = data.clone(); let data_ptr = data.as_ptr(); let data_len = data.len(); match submit_uring::( move |sqe| { sqe.opcode = IORING_OP_SEND; sqe.fd = fd; sqe.addr = data_ptr as u64; sqe.len = data_len as u32; sqe.op_flags = flags as u32; }, move |cqe| { let _data = data; cqe_to_result(cqe).map(|written| written as usize) }, ) .await { Err(error) if should_fallback_to_offload(&error) => { SEND_URING_SUPPORTED.with(|c| c.set(Some(false))); offload(move || send_sync(fd, fallback_data, flags)).await } result => { if result.is_ok() { SEND_URING_SUPPORTED.with(|c| c.set(Some(true))); } result } } } pub async fn send_to(op: NetOp) -> io::Result { let NetOp::SendTo { fd, target, data, flags, } = op else { unreachable!("send_to backend called with non-send_to op"); }; let raw_addr = Box::new(RawSocketAddr::from_socket_addr(target)); let mut iov = Box::new(libc::iovec { iov_base: data.as_ptr() as *mut c_void, iov_len: data.len(), }); let mut msg = Box::new(unsafe { std::mem::zeroed::() }); msg.msg_name = raw_addr.as_ptr() as *mut c_void; msg.msg_namelen = raw_addr.len(); msg.msg_iov = iov.as_mut() as *mut libc::iovec; msg.msg_iovlen = 1; let msg_ptr = msg.as_mut() as *mut libc::msghdr as u64; let iov = SendIovec(iov); let msg = SendMsghdr(msg); submit_uring::( move |sqe| { sqe.opcode = IORING_OP_SENDMSG; sqe.fd = fd; sqe.addr = msg_ptr; sqe.op_flags = flags as u32; }, move |cqe| { let _raw_addr = raw_addr; let _iov = iov; let _msg = msg; let _data = data; cqe_to_result(cqe).map(|written| written as usize) }, ) .await } pub async fn recv(op: NetOp) -> io::Result> { let NetOp::Recv { fd, len, flags } = op else { unreachable!("recv backend called with non-recv op"); }; let mut buffer = vec![0; len]; let buffer_ptr = buffer.as_mut_ptr(); let buffer_len = buffer.len(); match submit_uring::, _>( move |sqe| { sqe.opcode = IORING_OP_RECV; sqe.fd = fd; sqe.addr = buffer_ptr as u64; sqe.len = buffer_len as u32; sqe.op_flags = flags as u32; }, move |cqe| { let read = cqe_to_result(cqe)? as usize; buffer.truncate(read); Ok(buffer) }, ) .await { Err(error) if should_fallback_to_offload(&error) => { offload(move || recv_sync(fd, len, flags)).await } result => result, } } pub async fn recv_from(op: NetOp) -> io::Result { let NetOp::RecvFrom { fd, len, flags } = op else { unreachable!("recv_from backend called with non-recv_from op"); }; let mut data = vec![0u8; len]; let mut storage = Box::new(MaybeUninit::::zeroed()); let mut iov = Box::new(libc::iovec { iov_base: data.as_mut_ptr() as *mut c_void, iov_len: data.len(), }); let mut msg = Box::new(unsafe { std::mem::zeroed::() }); msg.msg_name = storage.as_mut_ptr() as *mut c_void; msg.msg_namelen = std::mem::size_of::() as libc::socklen_t; msg.msg_iov = iov.as_mut() as *mut libc::iovec; msg.msg_iovlen = 1; let msg_ptr = msg.as_mut() as *mut libc::msghdr as u64; let iov = SendIovec(iov); let msg = SendMsghdr(msg); match submit_uring::( move |sqe| { sqe.opcode = IORING_OP_RECVMSG; sqe.fd = fd; sqe.addr = msg_ptr; sqe.op_flags = flags as u32; }, move |cqe| { let _iov = iov; let addr_len = msg.0.msg_namelen; drop(msg); let read = cqe_to_result(cqe)? as usize; data.truncate(read); let storage = unsafe { storage.assume_init() }; let peer_addr = socket_addr_from_storage(&storage, addr_len)?; Ok(ReceivedDatagram { data, peer_addr }) }, ) .await { Err(error) if should_fallback_to_offload(&error) => { offload(move || recv_from_sync(fd, len, flags)).await } result => result, } } pub async fn shutdown(op: NetOp) -> io::Result<()> { let NetOp::Shutdown { fd, how } = op else { unreachable!("shutdown backend called with non-shutdown op"); }; let fallback_how = how; match submit_uring::<(), _>( move |sqe| { sqe.opcode = IORING_OP_SHUTDOWN; sqe.fd = fd; sqe.len = shutdown_how(how) as u32; }, move |cqe| cqe_to_result(cqe).map(|_| ()), ) .await { Err(error) if should_fallback_to_offload(&error) => { offload(move || shutdown_sync(fd, fallback_how)).await } result => result, } } pub async fn close(op: NetOp) -> io::Result<()> { let NetOp::Close { fd } = op else { unreachable!("close backend called with non-close op"); }; match submit_uring::<(), _>( move |sqe| { sqe.opcode = IORING_OP_CLOSE; sqe.fd = fd; }, move |cqe| cqe_to_result(cqe).map(|_| ()), ) .await { Err(error) if should_fallback_to_offload(&error) => offload(move || close_sync(fd)).await, result => result, } } pub async fn connect_stream(addr: SocketAddr) -> io::Result { let socket = socket(NetOp::Socket { domain: socket_domain(addr), socket_type: libc::SOCK_STREAM, protocol: 0, flags: libc::SOCK_CLOEXEC as u32, }) .await?; let connect_result = connect(NetOp::Connect { fd: socket.as_raw_fd(), addr, }) .await; match connect_result { Ok(()) => Ok(socket), Err(error) => Err(error), } } pub async fn bind_listener(addr: SocketAddr, backlog: Option) -> io::Result { let listener = socket(NetOp::Socket { domain: socket_domain(addr), socket_type: libc::SOCK_STREAM, protocol: 0, flags: libc::SOCK_CLOEXEC as u32, }) .await?; set_reuse_addr(listener.as_raw_fd(), true)?; bind(NetOp::Bind { fd: listener.as_raw_fd(), addr, }) .await?; listen(NetOp::Listen { fd: listener.as_raw_fd(), backlog: backlog.unwrap_or(DEFAULT_LISTENER_BACKLOG), }) .await?; Ok(listener) } pub async fn bind_datagram(addr: SocketAddr) -> io::Result { let socket = socket(NetOp::Socket { domain: socket_domain(addr), socket_type: libc::SOCK_DGRAM, protocol: 0, flags: libc::SOCK_CLOEXEC as u32, }) .await?; bind(NetOp::Bind { fd: socket.as_raw_fd(), addr, }) .await?; Ok(socket) } pub async fn duplicate(fd: RawFd) -> io::Result { offload(move || { let duplicated = cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) })?; Ok(unsafe { OwnedFd::from_raw_fd(duplicated) }) }) .await } pub async fn recv_timeout( fd: RawFd, len: usize, flags: i32, timeout: Duration, ) -> io::Result> { let mut buffer = vec![0u8; len]; let buffer_ptr = buffer.as_mut_ptr(); let buffer_len = buffer.len(); submit_uring_with_linked_timeout::, _>( move |sqe| { sqe.opcode = IORING_OP_RECV; sqe.fd = fd; sqe.addr = buffer_ptr as u64; sqe.len = buffer_len as u32; sqe.op_flags = flags as u32; }, timeout, move |cqe| { let read = cqe_to_timed_result(cqe)? as usize; buffer.truncate(read); Ok(buffer) }, ) .await } pub async fn send_timeout( fd: RawFd, data: Vec, flags: i32, timeout: Duration, ) -> io::Result { let data_ptr = data.as_ptr(); let data_len = data.len(); submit_uring_with_linked_timeout::( move |sqe| { sqe.opcode = IORING_OP_SEND; sqe.fd = fd; sqe.addr = data_ptr as u64; sqe.len = data_len as u32; sqe.op_flags = flags as u32; }, timeout, move |cqe| { let _data = data; cqe_to_timed_result(cqe).map(|written| written as usize) }, ) .await } pub async fn recv_from_timeout( fd: RawFd, len: usize, flags: i32, timeout: Duration, ) -> io::Result { let mut data = vec![0u8; len]; let mut storage = Box::new(MaybeUninit::::zeroed()); let mut iov = Box::new(libc::iovec { iov_base: data.as_mut_ptr() as *mut c_void, iov_len: data.len(), }); let mut msg = Box::new(unsafe { std::mem::zeroed::() }); msg.msg_name = storage.as_mut_ptr() as *mut c_void; msg.msg_namelen = std::mem::size_of::() as libc::socklen_t; msg.msg_iov = iov.as_mut() as *mut libc::iovec; msg.msg_iovlen = 1; let msg_ptr = msg.as_mut() as *mut libc::msghdr as u64; let iov = SendIovec(iov); let msg = SendMsghdr(msg); submit_uring_with_linked_timeout::( move |sqe| { sqe.opcode = IORING_OP_RECVMSG; sqe.fd = fd; sqe.addr = msg_ptr; sqe.op_flags = flags as u32; }, timeout, move |cqe| { let _iov = iov; let addr_len = msg.0.msg_namelen; drop(msg); let read = cqe_to_timed_result(cqe)? as usize; data.truncate(read); let storage = unsafe { storage.assume_init() }; let peer_addr = socket_addr_from_storage(&storage, addr_len)?; Ok(ReceivedDatagram { data, peer_addr }) }, ) .await } pub async fn send_to_timeout( fd: RawFd, data: Vec, target: SocketAddr, flags: i32, timeout: Duration, ) -> io::Result { let raw_addr = Box::new(RawSocketAddr::from_socket_addr(target)); let mut iov = Box::new(libc::iovec { iov_base: data.as_ptr() as *mut c_void, iov_len: data.len(), }); let mut msg = Box::new(unsafe { std::mem::zeroed::() }); msg.msg_name = raw_addr.as_ptr() as *mut c_void; msg.msg_namelen = raw_addr.len(); msg.msg_iov = iov.as_mut() as *mut libc::iovec; msg.msg_iovlen = 1; let msg_ptr = msg.as_mut() as *mut libc::msghdr as u64; let iov = SendIovec(iov); let msg = SendMsghdr(msg); submit_uring_with_linked_timeout::( move |sqe| { sqe.opcode = IORING_OP_SENDMSG; sqe.fd = fd; sqe.addr = msg_ptr; sqe.op_flags = flags as u32; }, timeout, move |cqe| { let _raw_addr = raw_addr; let _iov = iov; let _msg = msg; let _data = data; cqe_to_timed_result(cqe).map(|written| written as usize) }, ) .await } pub async fn connect_stream_timeout(addr: SocketAddr, timeout: Duration) -> io::Result { let socket = socket(NetOp::Socket { domain: socket_domain(addr), socket_type: libc::SOCK_STREAM, protocol: 0, flags: libc::SOCK_CLOEXEC as u32, }) .await?; let fd = socket.as_raw_fd(); let raw_addr = RawSocketAddr::from_socket_addr(addr); let addr_ptr = raw_addr.as_ptr(); let addr_len = raw_addr.len(); submit_uring_with_linked_timeout::<(), _>( move |sqe| { sqe.opcode = IORING_OP_CONNECT; sqe.fd = fd; sqe.addr = addr_ptr as u64; sqe.off = addr_len as u64; }, timeout, move |cqe| { let _raw_addr = raw_addr; cqe_to_timed_result(cqe).map(|_| ()) }, ) .await?; Ok(socket) } pub fn local_addr(fd: RawFd) -> io::Result { socket_addr_with(libc::getsockname, fd) } pub fn peer_addr(fd: RawFd) -> io::Result { socket_addr_with(libc::getpeername, fd) } pub fn nodelay(fd: RawFd) -> io::Result { let mut value = 0; let mut len = std::mem::size_of::() as libc::socklen_t; cvt(unsafe { libc::getsockopt( fd, libc::IPPROTO_TCP, libc::TCP_NODELAY, &mut value as *mut libc::c_int as *mut c_void, &mut len, ) })?; Ok(value != 0) } pub fn broadcast(fd: RawFd) -> io::Result { getsockopt_int(fd, libc::SOL_SOCKET, libc::SO_BROADCAST).map(|value| value != 0) } pub fn set_broadcast(fd: RawFd, enabled: bool) -> io::Result<()> { setsockopt_int(fd, libc::SOL_SOCKET, libc::SO_BROADCAST, enabled.into()) } pub fn ttl(fd: RawFd) -> io::Result { match socket_family(fd)? { libc::AF_INET => { getsockopt_int(fd, libc::IPPROTO_IP, libc::IP_TTL).map(|value| value as u32) } libc::AF_INET6 => getsockopt_int(fd, libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS) .map(|value| value as u32), family => Err(io::Error::new( io::ErrorKind::InvalidInput, format!("unsupported socket family {family} for TTL"), )), } } pub fn set_ttl(fd: RawFd, ttl: u32) -> io::Result<()> { let ttl = i32::try_from(ttl) .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "TTL exceeds i32 range"))?; match socket_family(fd)? { libc::AF_INET => setsockopt_int(fd, libc::IPPROTO_IP, libc::IP_TTL, ttl), libc::AF_INET6 => setsockopt_int(fd, libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS, ttl), family => Err(io::Error::new( io::ErrorKind::InvalidInput, format!("unsupported socket family {family} for TTL"), )), } } pub fn set_nodelay(fd: RawFd, enabled: bool) -> io::Result<()> { let value: libc::c_int = enabled.into(); cvt(unsafe { libc::setsockopt( fd, libc::IPPROTO_TCP, libc::TCP_NODELAY, &value as *const libc::c_int as *const c_void, std::mem::size_of_val(&value) as libc::socklen_t, ) }) .map(|_| ()) } pub type RecvFuture = Pin>> + 'static>>; pub type SendFuture = Pin> + 'static>>; pub type ShutdownFuture = Pin> + 'static>>; pub fn recv_future(fd: RawFd, len: usize) -> RecvFuture { Box::pin(recv(NetOp::Recv { fd, len, flags: 0 })) } pub fn send_future(fd: RawFd, data: Vec) -> SendFuture { Box::pin(send(NetOp::Send { fd, data, flags: 0 })) } pub fn shutdown_future(fd: RawFd, how: Shutdown) -> ShutdownFuture { Box::pin(shutdown(NetOp::Shutdown { fd, how })) } async fn submit_uring( fill: impl FnOnce(&mut IoUringSqe), map: M, ) -> io::Result where M: FnOnce(IoUringCqe) -> io::Result + Send + 'static, { let (future, handle) = completion_for_current_thread::>(); let callback_handle = handle.clone(); let token = with_current_driver(|driver| { driver.submit_operation(fill, move |cqe| { callback_handle.complete(map(cqe)); }) })?; handle.set_cancel(move || { let _ = with_current_driver(|driver| driver.cancel_operation(token)); }); future.await } /// Like [`submit_uring`] but pairs the main SQE with an `IORING_OP_LINK_TIMEOUT`. /// /// If the timeout elapses before the main op completes, the completion callback /// receives a CQE with `res = -ECANCELED`. Callers should map `-ECANCELED` to /// `io::ErrorKind::TimedOut`. async fn submit_uring_with_linked_timeout( fill: impl FnOnce(&mut IoUringSqe), timeout: Duration, map: M, ) -> io::Result where M: FnOnce(IoUringCqe) -> io::Result + Send + 'static, { let (future, handle) = completion_for_current_thread::>(); let callback_handle = handle.clone(); let token = with_current_driver(|driver| { driver.submit_operation_with_linked_timeout(fill, timeout, move |cqe| { callback_handle.complete(map(cqe)); }) })?; handle.set_cancel(move || { let _ = with_current_driver(|driver| driver.cancel_operation(token)); }); future.await } async fn offload( task: impl FnOnce() -> io::Result + Send + 'static, ) -> io::Result { let (future, handle) = completion_for_current_thread::>(); thread::Builder::new() .name("ruin-runtime-net-offload".into()) .spawn(move || handle.complete(task())) .map_err(io::Error::other)?; future.await } fn socket_domain(addr: SocketAddr) -> i32 { match addr { SocketAddr::V4(_) => libc::AF_INET, SocketAddr::V6(_) => libc::AF_INET6, } } fn shutdown_how(how: Shutdown) -> i32 { match how { Shutdown::Read => libc::SHUT_RD, Shutdown::Write => libc::SHUT_WR, Shutdown::Both => libc::SHUT_RDWR, } } fn socket_addr_with( op: unsafe extern "C" fn(RawFd, *mut libc::sockaddr, *mut libc::socklen_t) -> libc::c_int, fd: RawFd, ) -> io::Result { let mut storage = MaybeUninit::::zeroed(); let mut len = std::mem::size_of::() as libc::socklen_t; cvt(unsafe { op(fd, storage.as_mut_ptr().cast::(), &mut len) })?; let storage = unsafe { storage.assume_init() }; socket_addr_from_storage(&storage, len) } fn set_reuse_addr(fd: RawFd, enabled: bool) -> io::Result<()> { setsockopt_int(fd, libc::SOL_SOCKET, libc::SO_REUSEADDR, enabled.into()) } fn socket_family(fd: RawFd) -> io::Result { let mut storage = MaybeUninit::::zeroed(); let mut len = std::mem::size_of::() as libc::socklen_t; cvt(unsafe { libc::getsockname(fd, storage.as_mut_ptr().cast::(), &mut len) })?; let storage = unsafe { storage.assume_init() }; Ok(storage.ss_family as i32) } fn getsockopt_int(fd: RawFd, level: i32, name: i32) -> io::Result { let mut value = 0; let mut len = std::mem::size_of::() as libc::socklen_t; cvt(unsafe { libc::getsockopt( fd, level, name, &mut value as *mut libc::c_int as *mut c_void, &mut len, ) })?; Ok(value) } fn setsockopt_int(fd: RawFd, level: i32, name: i32, value: i32) -> io::Result<()> { cvt(unsafe { libc::setsockopt( fd, level, name, &value as *const libc::c_int as *const c_void, std::mem::size_of_val(&value) as libc::socklen_t, ) }) .map(|_| ()) } fn socket_addr_from_storage( storage: &libc::sockaddr_storage, len: libc::socklen_t, ) -> io::Result { match storage.ss_family as i32 { libc::AF_INET => { if len < std::mem::size_of::() as libc::socklen_t { return Err(io::Error::new( io::ErrorKind::InvalidData, "short IPv4 socket address from kernel", )); } let addr = unsafe { *(storage as *const _ as *const libc::sockaddr_in) }; Ok(SocketAddr::V4(SocketAddrV4::new( Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()), u16::from_be(addr.sin_port), ))) } libc::AF_INET6 => { if len < std::mem::size_of::() as libc::socklen_t { return Err(io::Error::new( io::ErrorKind::InvalidData, "short IPv6 socket address from kernel", )); } let addr = unsafe { *(storage as *const _ as *const libc::sockaddr_in6) }; Ok(SocketAddr::V6(SocketAddrV6::new( Ipv6Addr::from(addr.sin6_addr.s6_addr), u16::from_be(addr.sin6_port), addr.sin6_flowinfo, addr.sin6_scope_id, ))) } family => Err(io::Error::new( io::ErrorKind::InvalidData, format!("unsupported socket address family {family}"), )), } } #[derive(Clone, Copy)] struct RawSocketAddr { storage: libc::sockaddr_storage, len: libc::socklen_t, } impl RawSocketAddr { fn from_socket_addr(addr: SocketAddr) -> Self { match addr { SocketAddr::V4(addr) => { let sockaddr = libc::sockaddr_in { sin_family: libc::AF_INET as libc::sa_family_t, sin_port: addr.port().to_be(), sin_addr: libc::in_addr { s_addr: u32::from_ne_bytes(addr.ip().octets()), }, sin_zero: [0; 8], }; let mut storage = unsafe { MaybeUninit::::zeroed().assume_init() }; unsafe { std::ptr::write( &mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_in, sockaddr, ); } Self { storage, len: std::mem::size_of::() as libc::socklen_t, } } SocketAddr::V6(addr) => { let sockaddr = libc::sockaddr_in6 { sin6_family: libc::AF_INET6 as libc::sa_family_t, sin6_port: addr.port().to_be(), sin6_flowinfo: addr.flowinfo(), sin6_addr: libc::in6_addr { s6_addr: addr.ip().octets(), }, sin6_scope_id: addr.scope_id(), }; let mut storage = unsafe { MaybeUninit::::zeroed().assume_init() }; unsafe { std::ptr::write( &mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_in6, sockaddr, ); } Self { storage, len: std::mem::size_of::() as libc::socklen_t, } } } } fn as_ptr(&self) -> *const libc::sockaddr { &self.storage as *const libc::sockaddr_storage as *const libc::sockaddr } fn len(&self) -> libc::socklen_t { self.len } } fn cqe_to_result(cqe: IoUringCqe) -> io::Result { if cqe.res < 0 { Err(io::Error::from_raw_os_error(-cqe.res)) } else { Ok(cqe.res) } } /// Like [`cqe_to_result`] but maps `-ECANCELED` (timeout fired) to `TimedOut`. fn cqe_to_timed_result(cqe: IoUringCqe) -> io::Result { if cqe.res == -libc::ECANCELED { return Err(io::Error::new( io::ErrorKind::TimedOut, "socket operation timed out", )); } cqe_to_result(cqe) } fn cvt(value: libc::c_int) -> io::Result { if value == -1 { Err(io::Error::last_os_error()) } else { Ok(value) } } fn should_fallback_to_offload(error: &io::Error) -> bool { matches!( error.raw_os_error(), Some(libc::EINVAL | libc::ENOSYS | libc::EOPNOTSUPP) ) } fn socket_sync(domain: i32, socket_type: i32, protocol: i32, flags: u32) -> io::Result { let fd = cvt(unsafe { libc::socket(domain, socket_type | flags as i32, protocol) })?; Ok(unsafe { OwnedFd::from_raw_fd(fd) }) } fn connect_sync(fd: RawFd, addr: RawSocketAddr) -> io::Result<()> { cvt(unsafe { libc::connect(fd, addr.as_ptr(), addr.len()) }).map(|_| ()) } fn bind_sync(fd: RawFd, addr: RawSocketAddr) -> io::Result<()> { cvt(unsafe { libc::bind(fd, addr.as_ptr(), addr.len()) }).map(|_| ()) } fn listen_sync(fd: RawFd, backlog: i32) -> io::Result<()> { cvt(unsafe { libc::listen(fd, backlog) }).map(|_| ()) } fn accept_sync(fd: RawFd) -> io::Result { let mut storage = MaybeUninit::::zeroed(); let mut len = std::mem::size_of::() as libc::socklen_t; let accepted_fd = cvt(unsafe { libc::accept4( fd, storage.as_mut_ptr().cast::(), &mut len, libc::SOCK_CLOEXEC, ) })?; let storage = unsafe { storage.assume_init() }; let peer_addr = socket_addr_from_storage(&storage, len)?; Ok(AcceptedSocket { fd: accepted_fd, peer_addr, }) } fn send_sync(fd: RawFd, data: Vec, flags: i32) -> io::Result { let written = unsafe { libc::send(fd, data.as_ptr().cast::(), data.len(), flags) }; cvt_long(written).map(|written| written as usize) } fn recv_sync(fd: RawFd, len: usize, flags: i32) -> io::Result> { let mut buffer = vec![0; len]; let read = unsafe { libc::recv( fd, buffer.as_mut_ptr().cast::(), buffer.len(), flags, ) }; let read = cvt_long(read)? as usize; buffer.truncate(read); Ok(buffer) } fn recv_from_sync(fd: RawFd, len: usize, flags: i32) -> io::Result { let mut buffer = vec![0; len]; let mut storage = MaybeUninit::::zeroed(); let mut addr_len = std::mem::size_of::() as libc::socklen_t; let read = unsafe { libc::recvfrom( fd, buffer.as_mut_ptr().cast::(), buffer.len(), flags, storage.as_mut_ptr().cast::(), &mut addr_len, ) }; let read = cvt_long(read)? as usize; buffer.truncate(read); let storage = unsafe { storage.assume_init() }; let peer_addr = socket_addr_from_storage(&storage, addr_len)?; Ok(ReceivedDatagram { data: buffer, peer_addr, }) } fn shutdown_sync(fd: RawFd, how: Shutdown) -> io::Result<()> { cvt(unsafe { libc::shutdown(fd, shutdown_how(how)) }).map(|_| ()) } fn close_sync(fd: RawFd) -> io::Result<()> { cvt(unsafe { libc::close(fd) }).map(|_| ()) } /// Wrapper making `Box` sendable across the async CQE boundary. /// /// Safety: `iov_base` points into a `Vec` that is owned by the same /// closure, so the pointer is valid until the CQE fires and the closure drops. struct SendIovec(#[allow(dead_code)] Box); unsafe impl Send for SendIovec {} /// Wrapper making `Box` sendable across the async CQE boundary. /// /// Safety: all raw pointers inside the `msghdr` (`msg_name`, `msg_iov`) point /// into heap storage owned by the same closure, so they are valid until the /// CQE fires and the closure drops. struct SendMsghdr(Box); unsafe impl Send for SendMsghdr {} fn cvt_long(value: libc::ssize_t) -> io::Result { if value == -1 { Err(io::Error::last_os_error()) } else { Ok(value) } }