tor_general_addr/
general.rs

1//! Support for generalized addresses.
2//!
3//! We use the [`SocketAddr`] type in this module,
4//! when we want write code
5//! that can treat AF_UNIX addresses and internet addresses as a single type.
6//!
7//! As an alternative, you could also write your code to be generic
8//! over address, listener, provider, and stream types.
9//! That would give you the performance benefits of monomorphization
10//! over some corresponding costs in complexity and code size.
11//! Generally, it's better to use these types unless you know
12//! that the minor performance overhead here will matter in practice.
13
14use std::path::Path;
15use std::sync::Arc;
16
17use crate::unix;
18use std::{io::Error as IoError, net};
19
20#[cfg(target_os = "android")]
21use std::os::android::net::SocketAddrExt as _;
22#[cfg(target_os = "linux")]
23use std::os::linux::net::SocketAddrExt as _;
24
25/// Any address that Arti can listen on or connect to.
26///
27/// We use this type when we want to make streams
28/// without being concerned whether they are AF_UNIX streams, TCP streams, or so forth.
29///
30/// To avoid confusion, you might want to avoid importing this type directly.
31/// Instead, import [`rtcompat::general`](crate::general)
32/// and refer to this type as `general::SocketAddr`.
33///
34/// ## String representation
35///
36/// Any `general::SocketAddr` has up to two string representations:
37///
38/// 1. A _qualified_ representation, consisting of a schema
39///    (either "unix" or "inet"),
40///    followed by a single colon,
41///    followed by the address itself represented as a string.
42///
43///    Examples: `unix:/path/to/socket`, `inet:127.0.0.1:9999`,
44///    `inet:[::1]:9999`.
45///
46///    The "unnamed" AF_UNIX address is represented as `unix:`.
47///
48/// 2. A _unqualified_ representation,
49///    consisting of a `net::SocketAddr` address represented as a string.
50///
51///    Examples: `127.0.0.1:9999`,  `[::1]:9999`.
52///
53/// Note that not every `general::SocketAddr` has a string representation!
54/// Currently, the ones that might not be representable are:
55///
56///  - "Abstract" AF_UNIX addresses (a Linux feature)
57///  - AF_UNIX addresses whose path name is not UTF-8.
58///
59/// Note also that string representations may contain whitespace
60/// or other unusual characters.
61/// `/var/run/arti socket` is a valid filename,
62/// so `unix:/var/run/arti socket` is a valid representation.
63///
64/// We may add new schemas in the future.
65/// If we do, any new schema will begin with an ascii alphabetical character,
66/// and will consist only of ascii alphanumeric characters,
67/// the character `-`, and the character `_`.
68///
69/// ### Network address representation
70///
71/// When representing a `net::Socketaddr` address as a string,
72/// we use the formats implemented by [`std::net::SocketAddr`]'s
73/// `FromStr` implementation.  In contrast with the textual representations of
74/// [`Ipv4Addr`](std::net::Ipv4Addr) and [`Ipv6Addr`](std::net::Ipv6Addr),
75/// these formats are not currently very well specified by Rust.
76/// Therefore we describe them here:
77///   * A `SocketAddrV4` is encoded as:
78///     - an [IPv4 address],
79///     - a colon (`:`),
80///     - a 16-bit decimal integer.
81///   * A `SocketAddrV6` is encoded as:
82///     - a left square bracket (`[`),
83///     - an [IPv6 address],
84///     - optionally, a percent sign (`%`) and a 32-bit decimal integer
85///     - a right square bracket (`]`),
86///     - a colon (`:`),
87///     - a 16-bit decimal integer.
88///
89/// Note that the above implementation does not provide any way
90/// to encode the [`flowinfo`](std::net::SocketAddrV6::flowinfo) member
91/// of a `SocketAddrV6`.
92/// Any `flowinfo` information set in an address
93/// will therefore be lost when the address is encoded.
94///
95/// [IPv4 address]: https://doc.rust-lang.org/std/net/struct.Ipv4Addr.html#textual-representation
96/// [IPv6 address]: https://doc.rust-lang.org/std/net/struct.Ipv6Addr.html#textual-representation
97///
98/// TODO: We should try to get Rust's stdlib specify these formats, so we don't have to.
99/// There is an open PR at <https://github.com/rust-lang/rust/pull/131790>.
100#[derive(Clone, Debug, derive_more::From, derive_more::TryInto)]
101#[non_exhaustive]
102pub enum SocketAddr {
103    /// An IPv4 or IPv6 address on the internet.
104    Inet(net::SocketAddr),
105    /// A local AF_UNIX address.
106    ///
107    /// (Note that [`unix::SocketAddr`] is unconstructable on platforms where it is not supported.)
108    Unix(unix::SocketAddr),
109}
110
111impl SocketAddr {
112    /// Return a wrapper object that can be used to display this address.
113    ///
114    /// The resulting display might be lossy, depending on whether this address can be represented
115    /// as a string.
116    ///
117    /// The displayed format here is intentionally undocumented;
118    /// it may change in the future.
119    pub fn display_lossy(&self) -> DisplayLossy<'_> {
120        DisplayLossy(self)
121    }
122
123    /// If possible, return a qualified string representation for this address.
124    ///
125    /// Otherwise return None.
126    pub fn try_to_string(&self) -> Option<String> {
127        use SocketAddr::*;
128        match self {
129            Inet(sa) => Some(format!("inet:{}", sa)),
130            Unix(sa) => {
131                if sa.is_unnamed() {
132                    Some("unix:".to_string())
133                } else {
134                    sa.as_pathname()
135                        .and_then(Path::to_str)
136                        .map(|p| format!("unix:{}", p))
137                }
138            }
139        }
140    }
141
142    /// If this address has an associated filesystem path,
143    /// return that path.
144    pub fn as_pathname(&self) -> Option<&Path> {
145        match self {
146            SocketAddr::Inet(_) => None,
147            SocketAddr::Unix(socket_addr) => socket_addr.as_pathname(),
148        }
149    }
150}
151
152/// Lossy display for a [`SocketAddr`].
153pub struct DisplayLossy<'a>(&'a SocketAddr);
154
155impl<'a> std::fmt::Display for DisplayLossy<'a> {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        use SocketAddr::*;
158        match self.0 {
159            Inet(sa) => write!(f, "inet:{}", sa),
160            Unix(sa) => {
161                if let Some(path) = sa.as_pathname() {
162                    if let Some(path_str) = path.to_str() {
163                        write!(f, "unix:{}", path_str)
164                    } else {
165                        write!(f, "unix:{} [lossy]", path.to_string_lossy())
166                    }
167                } else if sa.is_unnamed() {
168                    write!(f, "unix:")
169                } else {
170                    write!(f, "unix:{:?} [lossy]", sa)
171                }
172            }
173        }
174    }
175}
176
177impl std::str::FromStr for SocketAddr {
178    type Err = AddrParseError;
179
180    fn from_str(s: &str) -> Result<Self, Self::Err> {
181        if s.starts_with(|c: char| (c.is_ascii_digit() || c == '[')) {
182            // This looks like an inet address, and cannot be a qualified address.
183            Ok(s.parse::<net::SocketAddr>()?.into())
184        } else if let Some((schema, remainder)) = s.split_once(':') {
185            match schema {
186                "unix" => Ok(unix::SocketAddr::from_pathname(remainder)?.into()),
187                "inet" => Ok(remainder.parse::<net::SocketAddr>()?.into()),
188                _ => Err(AddrParseError::UnrecognizedSchema(schema.to_string())),
189            }
190        } else {
191            Err(AddrParseError::NoSchema)
192        }
193    }
194}
195
196/// An error encountered while attempting to parse a [`SocketAddr`]
197#[derive(Clone, Debug, thiserror::Error)]
198#[non_exhaustive]
199pub enum AddrParseError {
200    /// Tried to parse an address with an unrecognized schema.
201    #[error("Address schema {0:?} unrecognized")]
202    UnrecognizedSchema(String),
203    /// Tried to parse a non inet-address with no schema.
204    #[error("Address did not look like internet, but had no address schema.")]
205    NoSchema,
206    /// Tried to parse an address as an AF_UNIX address, but failed.
207    #[error("Invalid AF_UNIX address")]
208    InvalidAfUnixAddress(#[source] Arc<IoError>),
209    /// Tried to parse an address as a inet address, but failed.
210    #[error("Invalid internet address")]
211    InvalidInetAddress(#[from] std::net::AddrParseError),
212}
213
214impl From<IoError> for AddrParseError {
215    fn from(e: IoError) -> Self {
216        Self::InvalidAfUnixAddress(Arc::new(e))
217    }
218}
219
220impl PartialEq for SocketAddr {
221    /// Return true if two `SocketAddr`s are equal.
222    ///
223    /// For `Inet` addresses, delegates to `std::net::SocketAddr::eq`.
224    ///
225    /// For `Unix` addresses, treats two addresses as equal if any of the following is true:
226    ///   - Both addresses have the same path.
227    ///   - Both addresses are unnamed.
228    ///   - (Linux only) Both addresses have the same abstract name.
229    ///
230    /// Addresses of different types are always unequal.
231    fn eq(&self, other: &Self) -> bool {
232        match (self, other) {
233            (Self::Inet(l0), Self::Inet(r0)) => l0 == r0,
234            #[cfg(unix)]
235            (Self::Unix(l0), Self::Unix(r0)) => {
236                // Sadly, std::os::unix::net::SocketAddr doesn't implement PartialEq.
237                //
238                // This requires us to make our own, and prevents us from providing Eq.
239                if l0.is_unnamed() && r0.is_unnamed() {
240                    return true;
241                }
242                if let (Some(a), Some(b)) = (l0.as_pathname(), r0.as_pathname()) {
243                    return a == b;
244                }
245                #[cfg(any(target_os = "android", target_os = "linux"))]
246                if let (Some(a), Some(b)) = (l0.as_abstract_name(), r0.as_abstract_name()) {
247                    return a == b;
248                }
249                false
250            }
251            _ => false,
252        }
253    }
254}
255
256#[cfg(feature = "arbitrary")]
257impl<'a> arbitrary::Arbitrary<'a> for SocketAddr {
258    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
259        /// Simple enumeration to select an address type.
260        #[allow(clippy::missing_docs_in_private_items)]
261        #[derive(arbitrary::Arbitrary)]
262        enum Kind {
263            V4,
264            V6,
265            #[cfg(unix)]
266            Unix,
267            #[cfg(any(target_os = "android", target_os = "linux"))]
268            UnixAbstract,
269        }
270        match u.arbitrary()? {
271            Kind::V4 => Ok(SocketAddr::Inet(
272                net::SocketAddrV4::new(u.arbitrary()?, u.arbitrary()?).into(),
273            )),
274            Kind::V6 => Ok(SocketAddr::Inet(
275                net::SocketAddrV6::new(
276                    u.arbitrary()?,
277                    u.arbitrary()?,
278                    u.arbitrary()?,
279                    u.arbitrary()?,
280                )
281                .into(),
282            )),
283            #[cfg(unix)]
284            Kind::Unix => {
285                let pathname: std::ffi::OsString = u.arbitrary()?;
286                Ok(SocketAddr::Unix(
287                    unix::SocketAddr::from_pathname(pathname)
288                        .map_err(|_| arbitrary::Error::IncorrectFormat)?,
289                ))
290            }
291            #[cfg(any(target_os = "android", target_os = "linux"))]
292            Kind::UnixAbstract => {
293                use std::os::linux::net::SocketAddrExt as _;
294                let name: &[u8] = u.arbitrary()?;
295                Ok(SocketAddr::Unix(
296                    unix::SocketAddr::from_abstract_name(name)
297                        .map_err(|_| arbitrary::Error::IncorrectFormat)?,
298                ))
299            }
300        }
301    }
302}
303
304#[cfg(test)]
305mod test {
306    // @@ begin test lint list maintained by maint/add_warning @@
307    #![allow(clippy::bool_assert_comparison)]
308    #![allow(clippy::clone_on_copy)]
309    #![allow(clippy::dbg_macro)]
310    #![allow(clippy::mixed_attributes_style)]
311    #![allow(clippy::print_stderr)]
312    #![allow(clippy::print_stdout)]
313    #![allow(clippy::single_char_pattern)]
314    #![allow(clippy::unwrap_used)]
315    #![allow(clippy::unchecked_duration_subtraction)]
316    #![allow(clippy::useless_vec)]
317    #![allow(clippy::needless_pass_by_value)]
318    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
319
320    use super::AddrParseError;
321    use crate::general;
322    use assert_matches::assert_matches;
323    #[cfg(unix)]
324    use std::os::unix::net as unix;
325    use std::{net, str::FromStr as _};
326
327    /// Parse `s` as a `net::SocketAddr`, and build a `general::SocketAddr` from it.
328    ///
329    /// Testing only. Panics on error.
330    fn from_inet(s: &str) -> general::SocketAddr {
331        let a: net::SocketAddr = s.parse().unwrap();
332        a.into()
333    }
334
335    #[test]
336    fn ok_inet() {
337        assert_eq!(
338            from_inet("127.0.0.1:9999"),
339            general::SocketAddr::from_str("127.0.0.1:9999").unwrap()
340        );
341        assert_eq!(
342            from_inet("127.0.0.1:9999"),
343            general::SocketAddr::from_str("inet:127.0.0.1:9999").unwrap()
344        );
345
346        assert_eq!(
347            from_inet("[::1]:9999"),
348            general::SocketAddr::from_str("[::1]:9999").unwrap()
349        );
350        assert_eq!(
351            from_inet("[::1]:9999"),
352            general::SocketAddr::from_str("inet:[::1]:9999").unwrap()
353        );
354
355        assert_ne!(
356            general::SocketAddr::from_str("127.0.0.1:9999").unwrap(),
357            general::SocketAddr::from_str("[::1]:9999").unwrap()
358        );
359
360        let ga1 = from_inet("127.0.0.1:9999");
361        assert_eq!(ga1.display_lossy().to_string(), "inet:127.0.0.1:9999");
362        assert_eq!(ga1.try_to_string().unwrap(), "inet:127.0.0.1:9999");
363
364        let ga2 = from_inet("[::1]:9999");
365        assert_eq!(ga2.display_lossy().to_string(), "inet:[::1]:9999");
366        assert_eq!(ga2.try_to_string().unwrap(), "inet:[::1]:9999");
367    }
368
369    /// Treat `s` as a path for an AF_UNIX socket, and build a `general::SocketAddr` from it.
370    ///
371    /// Testing only. Panics on error.
372    #[cfg(unix)]
373    fn from_pathname(s: impl AsRef<std::path::Path>) -> general::SocketAddr {
374        let a = unix::SocketAddr::from_pathname(s).unwrap();
375        a.into()
376    }
377    #[test]
378    #[cfg(unix)]
379    fn ok_unix() {
380        assert_eq!(
381            from_pathname("/some/path"),
382            general::SocketAddr::from_str("unix:/some/path").unwrap()
383        );
384        assert_eq!(
385            from_pathname("/another/path"),
386            general::SocketAddr::from_str("unix:/another/path").unwrap()
387        );
388        assert_eq!(
389            from_pathname("/path/with spaces"),
390            general::SocketAddr::from_str("unix:/path/with spaces").unwrap()
391        );
392        assert_ne!(
393            general::SocketAddr::from_str("unix:/some/path").unwrap(),
394            general::SocketAddr::from_str("unix:/another/path").unwrap()
395        );
396        assert_eq!(
397            from_pathname(""),
398            general::SocketAddr::from_str("unix:").unwrap()
399        );
400
401        let ga1 = general::SocketAddr::from_str("unix:/some/path").unwrap();
402        assert_eq!(ga1.display_lossy().to_string(), "unix:/some/path");
403        assert_eq!(ga1.try_to_string().unwrap(), "unix:/some/path");
404
405        let ga2 = general::SocketAddr::from_str("unix:/another/path").unwrap();
406        assert_eq!(ga2.display_lossy().to_string(), "unix:/another/path");
407        assert_eq!(ga2.try_to_string().unwrap(), "unix:/another/path");
408    }
409
410    #[test]
411    fn parse_err_inet() {
412        assert_matches!(
413            "1234567890:999".parse::<general::SocketAddr>(),
414            Err(AddrParseError::InvalidInetAddress(_))
415        );
416        assert_matches!(
417            "1z".parse::<general::SocketAddr>(),
418            Err(AddrParseError::InvalidInetAddress(_))
419        );
420        assert_matches!(
421            "[[77".parse::<general::SocketAddr>(),
422            Err(AddrParseError::InvalidInetAddress(_))
423        );
424
425        assert_matches!(
426            "inet:fred:9999".parse::<general::SocketAddr>(),
427            Err(AddrParseError::InvalidInetAddress(_))
428        );
429
430        assert_matches!(
431            "inet:127.0.0.1".parse::<general::SocketAddr>(),
432            Err(AddrParseError::InvalidInetAddress(_))
433        );
434
435        assert_matches!(
436            "inet:[::1]".parse::<general::SocketAddr>(),
437            Err(AddrParseError::InvalidInetAddress(_))
438        );
439    }
440
441    #[test]
442    fn parse_err_schemata() {
443        assert_matches!(
444            "fred".parse::<general::SocketAddr>(),
445            Err(AddrParseError::NoSchema)
446        );
447        assert_matches!(
448            "fred:".parse::<general::SocketAddr>(),
449            Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
450        );
451        assert_matches!(
452            "fred:hello".parse::<general::SocketAddr>(),
453            Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
454        );
455    }
456
457    #[test]
458    #[cfg(unix)]
459    fn display_unix_weird() {
460        use std::ffi::OsStr;
461        use std::os::unix::ffi::OsStrExt as _;
462
463        let a1 = from_pathname(OsStr::from_bytes(&[255, 255, 255, 255]));
464        assert!(a1.try_to_string().is_none());
465        assert_eq!(a1.display_lossy().to_string(), "unix:���� [lossy]");
466
467        let a2 = from_pathname("");
468        assert_eq!(a2.try_to_string().unwrap(), "unix:");
469        assert_eq!(a2.display_lossy().to_string(), "unix:");
470    }
471
472    #[test]
473    #[cfg(not(unix))]
474    fn parse_err_no_unix() {
475        assert_matches!(
476            "unix:".parse::<general::SocketAddr>(),
477            Err(AddrParseError::InvalidAfUnixAddress(_))
478        );
479        assert_matches!(
480            "unix:/any/path".parse::<general::SocketAddr>(),
481            Err(AddrParseError::InvalidAfUnixAddress(_))
482        );
483    }
484}