uni_addr/
unix.rs

1//! Platform-specific code for Unix-like systems
2
3use std::ffi::{OsStr, OsString};
4use std::hash::{Hash, Hasher};
5use std::os::unix::ffi::OsStrExt;
6use std::path::Path;
7use std::{fmt, fs, io};
8
9wrapper_lite::general_wrapper! {
10    #[wrapper_impl(Deref)]
11    #[derive(Clone)]
12    /// Wrapper over [`std::os::unix::net::SocketAddr`].
13    ///
14    /// See [`SocketAddr::new`] for more details.
15    pub struct SocketAddr(std::os::unix::net::SocketAddr);
16}
17
18impl SocketAddr {
19    /// Creates a new [`SocketAddr`] from its string representation.
20    ///
21    /// # Address Types
22    ///
23    /// - Strings starting with `@` or `\0` are parsed as abstract unix socket
24    ///   addresses (Linux-specific).
25    /// - All other strings are parsed as pathname unix socket addresses.
26    /// - Empty strings create unnamed unix socket addresses.
27    ///
28    /// # Notes
29    ///
30    /// This method accepts an [`OsStr`] and does not guarantee proper null
31    /// termination. While pathname addresses reject interior null bytes,
32    /// abstract addresses accept them silently, potentially causing unexpected
33    /// behavior (e.g., `\0abstract` differs from `\0abstract\0\0\0\0\0...`).
34    /// Use [`SocketAddr::new_strict`] to ensure the abstract names do not
35    /// contain null bytes, too.
36    ///
37    /// # Examples
38    ///
39    /// ```rust
40    /// # use uni_addr::unix::SocketAddr;
41    /// #[cfg(any(target_os = "android", target_os = "linux"))]
42    /// // Abstract address (Linux-specific)
43    /// let abstract_addr = SocketAddr::new("@abstract.example.socket").unwrap();
44    /// // Pathname address
45    /// let pathname_addr = SocketAddr::new("/run/pathname.example.socket").unwrap();
46    /// // Unnamed address
47    /// let unnamed_addr = SocketAddr::new("").unwrap();
48    /// ```
49    ///
50    /// # Errors
51    ///
52    /// Returns an error if the address is invalid or unsupported on the
53    /// current platform.
54    ///
55    /// See [`SocketAddr::from_abstract_name`](std::os::linux::net::SocketAddrExt::from_abstract_name)
56    /// and [`StdSocketAddr::from_pathname`] for more details.
57    pub fn new<S: AsRef<OsStr> + ?Sized>(addr: &S) -> io::Result<Self> {
58        let addr = addr.as_ref();
59
60        match addr.as_bytes() {
61            #[cfg(any(target_os = "android", target_os = "linux"))]
62            [b'@', rest @ ..] | [b'\0', rest @ ..] => Self::new_abstract(rest),
63            #[cfg(not(any(target_os = "android", target_os = "linux")))]
64            [b'@', ..] | [b'\0', ..] => Err(io::Error::new(
65                io::ErrorKind::Unsupported,
66                "abstract unix socket address is not supported",
67            )),
68            _ => Self::new_pathname(addr),
69        }
70    }
71
72    /// See [`SocketAddr::new`].
73    pub fn new_strict<S: AsRef<OsStr> + ?Sized>(addr: &S) -> io::Result<Self> {
74        let addr = addr.as_ref();
75
76        match addr.as_bytes() {
77            #[cfg(any(target_os = "android", target_os = "linux"))]
78            [b'@', rest @ ..] | [b'\0', rest @ ..] => Self::new_abstract_strict(rest),
79            #[cfg(not(any(target_os = "android", target_os = "linux")))]
80            [b'@', ..] | [b'\0', ..] => Err(io::Error::new(
81                io::ErrorKind::Unsupported,
82                "abstract unix socket address is not supported",
83            )),
84            _ => Self::new_pathname(addr),
85        }
86    }
87
88    #[cfg(any(target_os = "android", target_os = "linux"))]
89    /// Creates a Unix socket address in the abstract namespace.
90    ///
91    /// The abstract namespace is a Linux-specific extension that allows Unix
92    /// sockets to be bound without creating an entry in the filesystem.
93    /// Abstract sockets are unaffected by filesystem layout or permissions, and
94    /// no cleanup is necessary when the socket is closed.
95    ///
96    /// An abstract socket address name may contain any bytes, including zero.
97    /// However, we don't recommend using zero bytes, as they may lead to
98    /// unexpected behavior. To avoid this, consider using
99    /// [`new_abstract_strict`](Self::new_abstract_strict).
100    ///
101    /// # Errors
102    ///
103    /// Returns an error if the name is longer than `SUN_LEN - 1`.
104    pub fn new_abstract(bytes: &[u8]) -> io::Result<Self> {
105        use std::os::linux::net::SocketAddrExt;
106
107        std::os::unix::net::SocketAddr::from_abstract_name(bytes).map(Self::const_from)
108    }
109
110    #[cfg(any(target_os = "android", target_os = "linux"))]
111    /// See [`SocketAddr::new_abstract`].
112    pub fn new_abstract_strict(bytes: &[u8]) -> io::Result<Self> {
113        use std::os::linux::net::SocketAddrExt;
114
115        if bytes.contains(&b'\0') {
116            return Err(io::Error::new(
117                io::ErrorKind::InvalidInput,
118                "parse abstract socket name in strict mode: reject NULL bytes",
119            ));
120        }
121
122        std::os::unix::net::SocketAddr::from_abstract_name(bytes).map(Self::const_from)
123    }
124
125    /// Constructs a [`SocketAddr`] with the family `AF_UNIX` and the provided
126    /// path.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if the path is longer than `SUN_LEN` or if it contains
131    /// NULL bytes.
132    pub fn new_pathname<P: AsRef<Path>>(pathname: P) -> io::Result<Self> {
133        let _ = fs::remove_file(pathname.as_ref());
134
135        std::os::unix::net::SocketAddr::from_pathname(pathname).map(Self::const_from)
136    }
137
138    #[allow(clippy::missing_panics_doc)]
139    /// Creates an unnamed [`SocketAddr`].
140    pub fn new_unnamed() -> Self {
141        // SAFETY: `from_pathname` will not fail at all.
142        std::os::unix::net::SocketAddr::from_pathname("")
143            .map(Self::const_from)
144            .unwrap()
145    }
146
147    #[inline]
148    /// Creates a new [`SocketAddr`] from bytes.
149    ///
150    /// # Errors
151    ///
152    /// See [`SocketAddr::new`].
153    pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
154        Self::new(OsStr::from_bytes(bytes))
155    }
156
157    /// Serializes the [`SocketAddr`] to an `OsString`.
158    ///
159    /// # Returns
160    ///
161    /// - For abstract ones: returns the name prefixed with **`\0`**
162    /// - For pathname ones: returns the pathname
163    /// - For unnamed ones: returns an empty string.
164    pub fn to_os_string(&self) -> OsString {
165        self.to_os_string_impl("", "\0")
166    }
167
168    /// Likes [`to_os_string`](Self::to_os_string), but returns a `String`
169    /// instead of `OsString`, performing lossy UTF-8 conversion.
170    ///
171    /// # Returns
172    ///
173    /// - For abstract ones: returns the name prefixed with **`@`**
174    /// - For pathname ones: returns the pathname
175    /// - For unnamed ones: returns an empty string.
176    pub fn to_string_lossy(&self) -> String {
177        self.to_os_string_impl("", "@")
178            .to_string_lossy()
179            .into_owned()
180    }
181
182    pub(crate) fn to_os_string_impl(&self, prefix: &str, abstract_identifier: &str) -> OsString {
183        let mut os_string = OsString::from(prefix);
184
185        if let Some(pathname) = self.as_pathname() {
186            // Notice: cannot use `extend` here
187            os_string.push(pathname);
188
189            return os_string;
190        }
191
192        #[cfg(any(target_os = "android", target_os = "linux"))]
193        {
194            use std::os::linux::net::SocketAddrExt;
195
196            if let Some(abstract_name) = self.as_abstract_name() {
197                os_string.push(abstract_identifier);
198                os_string.push(OsStr::from_bytes(abstract_name));
199
200                return os_string;
201            }
202        }
203
204        // An unnamed one...
205        os_string
206    }
207}
208
209impl fmt::Debug for SocketAddr {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        self.as_inner().fmt(f)
212    }
213}
214
215impl PartialEq for SocketAddr {
216    fn eq(&self, other: &Self) -> bool {
217        if let Some((l, r)) = self.as_pathname().zip(other.as_pathname()) {
218            return l == r;
219        }
220
221        #[cfg(any(target_os = "android", target_os = "linux"))]
222        {
223            use std::os::linux::net::SocketAddrExt;
224
225            if let Some((l, r)) = self.as_abstract_name().zip(other.as_abstract_name()) {
226                return l == r;
227            }
228        }
229
230        if self.is_unnamed() && other.is_unnamed() {
231            return true;
232        }
233
234        false
235    }
236}
237
238impl Eq for SocketAddr {}
239
240impl Hash for SocketAddr {
241    fn hash<H: Hasher>(&self, state: &mut H) {
242        if let Some(pathname) = self.as_pathname() {
243            pathname.hash(state);
244
245            return;
246        }
247
248        #[cfg(any(target_os = "android", target_os = "linux"))]
249        {
250            use std::os::linux::net::SocketAddrExt;
251
252            if let Some(abstract_name) = self.as_abstract_name() {
253                b'\0'.hash(state);
254                abstract_name.hash(state);
255
256                return;
257            }
258        }
259
260        debug_assert!(self.is_unnamed(), "SocketAddr is not unnamed one");
261
262        // `Path` cannot contain null bytes, and abstract names are started with
263        // null bytes, this is Ok.
264        b"(unnamed)\0".hash(state);
265    }
266}
267
268#[cfg(feature = "feat-serde")]
269impl serde::Serialize for SocketAddr {
270    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
271    where
272        S: serde::Serializer,
273    {
274        serializer.serialize_str(&self.to_string_lossy())
275    }
276}
277
278#[cfg(feature = "feat-serde")]
279impl<'de> serde::Deserialize<'de> for SocketAddr {
280    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
281    where
282        D: serde::Deserializer<'de>,
283    {
284        Self::new(<&str>::deserialize(deserializer)?).map_err(serde::de::Error::custom)
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use core::hash::{Hash, Hasher};
291    use std::hash::DefaultHasher;
292
293    use super::*;
294
295    #[test]
296    fn test_unnamed() {
297        const TEST_CASE: &str = "";
298
299        let addr = SocketAddr::new(TEST_CASE).unwrap();
300
301        assert!(addr.as_ref().is_unnamed());
302    }
303
304    #[test]
305    fn test_pathname() {
306        const TEST_CASE: &str = "/tmp/test_pathname.socket";
307
308        let addr = SocketAddr::new(TEST_CASE).unwrap();
309
310        assert_eq!(addr.to_os_string().to_str().unwrap(), TEST_CASE);
311        assert_eq!(addr.to_string_lossy(), TEST_CASE);
312        assert_eq!(addr.as_pathname().unwrap().to_str().unwrap(), TEST_CASE);
313    }
314
315    #[test]
316    #[cfg(any(target_os = "android", target_os = "linux"))]
317    fn test_abstract() {
318        use std::os::linux::net::SocketAddrExt;
319
320        const TEST_CASE_1: &[u8] = b"@abstract.socket";
321        const TEST_CASE_2: &[u8] = b"\0abstract.socket";
322        const TEST_CASE_3: &[u8] = b"@";
323        const TEST_CASE_4: &[u8] = b"\0";
324
325        assert_eq!(
326            SocketAddr::new(OsStr::from_bytes(TEST_CASE_1))
327                .unwrap()
328                .as_abstract_name()
329                .unwrap(),
330            &TEST_CASE_1[1..]
331        );
332
333        assert_eq!(
334            SocketAddr::new(OsStr::from_bytes(TEST_CASE_2))
335                .unwrap()
336                .as_abstract_name()
337                .unwrap(),
338            &TEST_CASE_2[1..]
339        );
340
341        assert_eq!(
342            SocketAddr::new(OsStr::from_bytes(TEST_CASE_3))
343                .unwrap()
344                .as_abstract_name()
345                .unwrap(),
346            &TEST_CASE_3[1..]
347        );
348
349        assert_eq!(
350            SocketAddr::new(OsStr::from_bytes(TEST_CASE_4))
351                .unwrap()
352                .as_abstract_name()
353                .unwrap(),
354            &TEST_CASE_4[1..]
355        );
356    }
357
358    #[test]
359    #[should_panic]
360    fn test_pathname_with_null_byte() {
361        let _addr = SocketAddr::new_pathname("(unamed)\0").unwrap();
362    }
363
364    #[test]
365    fn test_partial_eq_hash() {
366        let addr_pathname_1 = SocketAddr::new("/tmp/test_pathname_1.socket").unwrap();
367        let addr_pathname_2 = SocketAddr::new("/tmp/test_pathname_2.socket").unwrap();
368        let addr_unnamed = SocketAddr::new_unnamed();
369
370        assert_eq!(addr_pathname_1, addr_pathname_1);
371        assert_ne!(addr_pathname_1, addr_pathname_2);
372        assert_ne!(addr_pathname_2, addr_pathname_1);
373
374        assert_eq!(addr_unnamed, addr_unnamed);
375        assert_ne!(addr_pathname_1, addr_unnamed);
376        assert_ne!(addr_unnamed, addr_pathname_1);
377        assert_ne!(addr_pathname_2, addr_unnamed);
378        assert_ne!(addr_unnamed, addr_pathname_2);
379
380        #[cfg(any(target_os = "android", target_os = "linux"))]
381        {
382            let addr_abstract_1 = SocketAddr::new_abstract(b"/tmp/test_pathname_1.socket").unwrap();
383            let addr_abstract_2 = SocketAddr::new_abstract(b"/tmp/test_pathname_2.socket").unwrap();
384            let addr_abstract_empty = SocketAddr::new_abstract(&[]).unwrap();
385            let addr_abstract_unnamed_hash = SocketAddr::new_abstract(b"(unamed)\0").unwrap();
386
387            assert_eq!(addr_abstract_1, addr_abstract_1);
388            assert_ne!(addr_abstract_1, addr_abstract_2);
389            assert_ne!(addr_abstract_2, addr_abstract_1);
390
391            // Empty abstract addresses should be equal to unnamed addresses
392            assert_ne!(addr_unnamed, addr_abstract_empty);
393
394            // Abstract addresses should not be equal to pathname addresses
395            assert_ne!(addr_pathname_1, addr_abstract_1);
396
397            // Abstract unnamed address `@(unamed)\0`' hash should not be equal to unname
398            // ones'
399            let addr_unnamed_hash = {
400                let mut state = DefaultHasher::new();
401                addr_unnamed.hash(&mut state);
402                state.finish()
403            };
404            let addr_abstract_unnamed_hash = {
405                let mut state = DefaultHasher::new();
406                addr_abstract_unnamed_hash.hash(&mut state);
407                state.finish()
408            };
409            assert_ne!(addr_unnamed_hash, addr_abstract_unnamed_hash);
410        }
411    }
412}