Skip to main content

ax_net/unix/
mod.rs

1//! Unix domain socket facade.
2//!
3//! This module provides the shared address namespace and transport dispatch for
4//! Unix stream and datagram sockets. The concrete transports live in
5//! `stream.rs` and `dgram.rs`; this layer handles bind/connect/accept plumbing
6//! and exposes them through the common socket API.
7//!
8//! # Namespace Model
9//!
10//! Abstract names are stored in an in-memory map owned by ax-net. Path names are
11//! delegated to an optional filesystem namespace provider so the socket layer
12//! does not depend on a concrete VFS implementation.
13//!
14//! # Transport Split
15//!
16//! `UnixSocket` owns local/remote address state and a protocol-erased
17//! `Transport`. Stream and datagram transports implement the actual byte-stream
18//! or message semantics, including cmsg handling and poll readiness.
19
20pub(crate) mod dgram;
21pub mod namespace;
22pub(crate) mod stream;
23
24use alloc::sync::Arc;
25
26use ax_io::{IoBuf, Read, Write};
27use ax_lazyinit::LazyLock;
28use ax_sync::SpinLock;
29use axpoll::{ExclusiveRegistrationSink, IoEvents, Pollable, SharedRegistrationSink};
30use axpoll_set::PollSet;
31use enum_dispatch::enum_dispatch;
32use hashbrown::HashMap;
33
34pub use self::{
35    dgram::DgramTransport,
36    namespace::{UnixNamespace, register_unix_namespace},
37    stream::StreamTransport,
38};
39use crate::{
40    ConnectStatus, NetError, NetResult, RecvOptions, SendOptions, Shutdown, Socket, SocketAddrEx,
41    SocketOps,
42    options::{Configurable, GetSocketOption, SetSocketOption},
43};
44
45/// Address for a Unix domain socket.
46#[derive(Default, Clone, Debug)]
47pub enum UnixSocketAddr {
48    /// Unnamed (anonymous) socket.
49    #[default]
50    Unnamed,
51    /// Abstract namespace address.
52    Abstract(Arc<[u8]>),
53    /// Filesystem path address.
54    Path(Arc<str>),
55}
56
57/// Abstract transport trait for Unix sockets.
58#[enum_dispatch]
59pub trait TransportOps: Configurable + Pollable + Send + Sync {
60    /// Bind the transport to the given address.
61    fn bind(&self, slot: &BindSlot, local_addr: &UnixSocketAddr) -> NetResult;
62    /// Connect the transport to a remote address and return an accept poll set
63    /// that must be woken after the namespace and socket-state locks are released.
64    fn connect(
65        &self,
66        slot: &BindSlot,
67        local_addr: &UnixSocketAddr,
68    ) -> NetResult<Option<Arc<PollSet>>>;
69
70    /// Marks a bound connection-oriented transport as accepting connections.
71    fn listen(&self) -> NetResult {
72        Err(NetError::OperationNotSupported)
73    }
74
75    /// Returns whether this transport currently accepts connections.
76    fn is_listening(&self) -> bool {
77        false
78    }
79
80    /// Non-blocking accept: returns `WouldBlock` immediately when no connection is pending.
81    fn try_accept(&self) -> NetResult<(Transport, UnixSocketAddr)> {
82        Err(NetError::WouldBlock)
83    }
84
85    /// Send data through the transport.
86    fn try_send(&self, src: impl Read + IoBuf, options: &mut SendOptions) -> NetResult<usize>;
87    /// Receive data from the transport.
88    fn try_recv(&self, dst: impl Write, options: &mut RecvOptions<'_>) -> NetResult<usize>;
89
90    /// Shutdown the transport.
91    fn shutdown(&self, _how: Shutdown) -> NetResult {
92        Ok(())
93    }
94}
95
96/// Unix domain transport type (stream or datagram).
97#[enum_dispatch(Configurable, TransportOps)]
98pub enum Transport {
99    /// Stream-oriented transport.
100    Stream(StreamTransport),
101    /// Datagram-oriented transport.
102    Dgram(DgramTransport),
103}
104impl Transport {
105    fn finish_connect(&self, accept_poll: Option<Arc<PollSet>>) {
106        if let Some(poll) = accept_poll {
107            // The connection request and both endpoint states are visible, and
108            // no namespace or transport lock is held while wakers run.
109            unsafe { poll.wake(IoEvents::IN) };
110        }
111        match self {
112            Transport::Stream(stream) => stream.wake_connected(),
113            Transport::Dgram(dgram) => dgram.wake_connected(),
114        }
115    }
116}
117impl Pollable for Transport {
118    fn poll(&self) -> IoEvents {
119        match self {
120            Transport::Stream(stream) => stream.poll(),
121            Transport::Dgram(dgram) => dgram.poll(),
122        }
123    }
124
125    unsafe fn register_shared(&self, sink: &mut dyn SharedRegistrationSink, events: IoEvents) {
126        match self {
127            Transport::Stream(stream) => unsafe { stream.register_shared(sink, events) },
128            Transport::Dgram(dgram) => unsafe { dgram.register_shared(sink, events) },
129        }
130    }
131
132    unsafe fn register_exclusive(
133        &self,
134        sink: &mut dyn ExclusiveRegistrationSink,
135        events: IoEvents,
136    ) {
137        match self {
138            Transport::Stream(stream) => unsafe { stream.register_exclusive(sink, events) },
139            Transport::Dgram(dgram) => unsafe { dgram.register_exclusive(sink, events) },
140        }
141    }
142}
143
144/// Holds binding state for stream and datagram transports at a Unix address.
145#[derive(Default)]
146pub struct BindSlot {
147    /// Stream listener bound at this address.
148    stream: SpinLock<Option<stream::Bind>>,
149    /// Datagram endpoint bound at this address.
150    dgram: SpinLock<Option<dgram::Bind>>,
151    /// Seqpacket listener bound at this address. Seqpacket is connection
152    /// oriented (like stream) but preserves message boundaries (like dgram),
153    /// so it carries its own connection-request queue.
154    seqpacket: SpinLock<Option<dgram::SeqBind>>,
155}
156
157static ABSTRACT_BINDS: LazyLock<SpinLock<HashMap<Arc<[u8]>, BindSlot>>> =
158    LazyLock::new(|| SpinLock::new(HashMap::new()));
159
160/// Resolves an existing bind slot and runs `f` with it.
161pub(crate) fn with_slot<R>(
162    addr: &UnixSocketAddr,
163    f: impl FnOnce(&BindSlot) -> NetResult<R>,
164) -> NetResult<R> {
165    match addr {
166        UnixSocketAddr::Unnamed => Err(NetError::InvalidInput),
167        UnixSocketAddr::Abstract(name) => {
168            let binds = ABSTRACT_BINDS.lock();
169            if let Some(slot) = binds.get(name) {
170                f(slot)
171            } else {
172                Err(NetError::NotFound)
173            }
174        }
175        UnixSocketAddr::Path(path) => namespace::with_namespace(|ns| {
176            let slot = ns.resolve(path.as_ref())?;
177            f(slot.as_ref())
178        }),
179    }
180}
181/// Resolves or creates a bind slot and runs `f` with it.
182fn with_slot_or_insert<R>(
183    addr: &UnixSocketAddr,
184    f: impl FnOnce(&BindSlot) -> NetResult<R>,
185) -> NetResult<R> {
186    match addr {
187        UnixSocketAddr::Unnamed => Err(NetError::InvalidInput),
188        UnixSocketAddr::Abstract(name) => {
189            let mut binds = ABSTRACT_BINDS.lock();
190            f(binds.entry(name.clone()).or_default())
191        }
192        UnixSocketAddr::Path(path) => namespace::with_namespace(|ns| {
193            let slot = ns.bind(path.as_ref())?;
194            f(slot.as_ref())
195        }),
196    }
197}
198
199/// A Unix domain socket.
200pub struct UnixSocket {
201    /// Concrete stream or datagram transport.
202    transport: Transport,
203    /// Public local Unix address.
204    local_addr: SpinLock<UnixSocketAddr>,
205    /// Public remote Unix address.
206    remote_addr: SpinLock<UnixSocketAddr>,
207}
208impl UnixSocket {
209    /// Create a new Unix socket with the given transport.
210    pub fn new(transport: impl Into<Transport>) -> Self {
211        Self {
212            transport: transport.into(),
213            local_addr: SpinLock::new(UnixSocketAddr::Unnamed),
214            remote_addr: SpinLock::new(UnixSocketAddr::Unnamed),
215        }
216    }
217}
218impl Configurable for UnixSocket {
219    fn get_option_inner(&self, opt: &mut GetSocketOption) -> NetResult<bool> {
220        self.transport.get_option_inner(opt)
221    }
222
223    fn set_option_inner(&self, opt: SetSocketOption) -> NetResult<bool> {
224        self.transport.set_option_inner(opt)
225    }
226}
227impl SocketOps for UnixSocket {
228    fn bind(&self, local_addr: SocketAddrEx) -> NetResult {
229        let local_addr = local_addr.into_unix()?;
230        let mut guard = self.local_addr.lock();
231        if matches!(&*guard, UnixSocketAddr::Unnamed) {
232            with_slot_or_insert(&local_addr, |slot| self.transport.bind(slot, &local_addr))?;
233            *guard = local_addr;
234        } else {
235            return Err(NetError::InvalidInput);
236        }
237        Ok(())
238    }
239
240    fn start_connect(&self, remote_addr: SocketAddrEx) -> NetResult<ConnectStatus> {
241        let remote_addr = remote_addr.into_unix()?;
242        let local_addr = self.local_addr.lock().clone();
243        let accept_poll = {
244            let mut guard = self.remote_addr.lock();
245            if !matches!(&*guard, UnixSocketAddr::Unnamed) {
246                return Err(NetError::InvalidInput);
247            }
248            let accept_poll = with_slot(&remote_addr, |slot| {
249                self.transport.connect(slot, &local_addr)
250            })?;
251            *guard = remote_addr;
252            accept_poll
253        };
254        self.transport.finish_connect(accept_poll);
255        Ok(ConnectStatus::Connected)
256    }
257
258    fn listen(&self, _backlog: usize) -> NetResult {
259        self.transport.listen()
260    }
261
262    fn is_listening(&self) -> bool {
263        self.transport.is_listening()
264    }
265
266    fn try_accept(&self) -> NetResult<Socket> {
267        let (transport, peer_addr) = self.transport.try_accept()?;
268        Ok(Self {
269            transport,
270            local_addr: SpinLock::new(self.local_addr.lock().clone()),
271            remote_addr: SpinLock::new(peer_addr),
272        }
273        .into())
274    }
275
276    fn try_send(&self, src: impl Read + IoBuf, options: &mut SendOptions) -> NetResult<usize> {
277        self.transport.try_send(src, options)
278    }
279
280    fn try_recv(&self, dst: impl Write, options: &mut RecvOptions<'_>) -> NetResult<usize> {
281        self.transport.try_recv(dst, options)
282    }
283
284    fn local_addr(&self) -> NetResult<SocketAddrEx> {
285        Ok(SocketAddrEx::Unix(self.local_addr.lock().clone()))
286    }
287
288    fn peer_addr(&self) -> NetResult<SocketAddrEx> {
289        Ok(SocketAddrEx::Unix(self.remote_addr.lock().clone()))
290    }
291
292    fn shutdown(&self, how: Shutdown) -> NetResult {
293        self.transport.shutdown(how)
294    }
295}
296
297impl Pollable for UnixSocket {
298    fn poll(&self) -> IoEvents {
299        self.transport.poll()
300    }
301
302    unsafe fn register_shared(&self, sink: &mut dyn SharedRegistrationSink, events: IoEvents) {
303        unsafe { self.transport.register_shared(sink, events) };
304    }
305
306    unsafe fn register_exclusive(
307        &self,
308        sink: &mut dyn ExclusiveRegistrationSink,
309        events: IoEvents,
310    ) {
311        unsafe { self.transport.register_exclusive(sink, events) };
312    }
313}