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

            
14
use std::path::Path;
15
use std::sync::Arc;
16

            
17
use crate::unix;
18
use std::{io::Error as IoError, net};
19

            
20
#[cfg(target_os = "android")]
21
use std::os::android::net::SocketAddrExt as _;
22
#[cfg(target_os = "linux")]
23
use 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" 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]
102
pub 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

            
111
impl 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
12
    pub fn display_lossy(&self) -> DisplayLossy<'_> {
120
12
        DisplayLossy(self)
121
12
    }
122

            
123
    /// If possible, return a qualified string representation for this address.
124
    ///
125
    /// Otherwise return None.
126
12
    pub fn try_to_string(&self) -> Option<String> {
127
12
        use SocketAddr::*;
128
12
        match self {
129
4
            Inet(sa) => Some(format!("inet:{}", sa)),
130
8
            Unix(sa) => {
131
8
                if sa.is_unnamed() {
132
2
                    Some("unix:".to_string())
133
                } else {
134
6
                    sa.as_pathname()
135
6
                        .and_then(Path::to_str)
136
8
                        .map(|p| format!("unix:{}", p))
137
                }
138
            }
139
        }
140
12
    }
141
}
142

            
143
/// Lossy display for a [`SocketAddr`].
144
pub struct DisplayLossy<'a>(&'a SocketAddr);
145

            
146
impl<'a> std::fmt::Display for DisplayLossy<'a> {
147
12
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148
12
        use SocketAddr::*;
149
12
        match self.0 {
150
4
            Inet(sa) => write!(f, "inet:{}", sa),
151
8
            Unix(sa) => {
152
8
                if let Some(path) = sa.as_pathname() {
153
6
                    if let Some(path_str) = path.to_str() {
154
4
                        write!(f, "unix:{}", path_str)
155
                    } else {
156
2
                        write!(f, "unix:{} [lossy]", path.to_string_lossy())
157
                    }
158
2
                } else if sa.is_unnamed() {
159
2
                    write!(f, "unix:")
160
                } else {
161
                    write!(f, "unix:{:?} [lossy]", sa)
162
                }
163
            }
164
        }
165
12
    }
166
}
167

            
168
impl std::str::FromStr for SocketAddr {
169
    type Err = AddrParseError;
170

            
171
346
    fn from_str(s: &str) -> Result<Self, Self::Err> {
172
373
        if s.starts_with(|c: char| (c.is_ascii_digit() || c == '[')) {
173
            // This looks like an inet address, and cannot be a qualified address.
174
164
            Ok(s.parse::<net::SocketAddr>()?.into())
175
182
        } else if let Some((schema, remainder)) = s.split_once(':') {
176
180
            match schema {
177
180
                "unix" => Ok(unix::SocketAddr::from_pathname(remainder)?.into()),
178
164
                "inet" => Ok(remainder.parse::<net::SocketAddr>()?.into()),
179
4
                _ => Err(AddrParseError::UnrecognizedSchema(schema.to_string())),
180
            }
181
        } else {
182
2
            Err(AddrParseError::NoSchema)
183
        }
184
346
    }
185
}
186

            
187
/// An error encountered while attempting to parse a [`SocketAddr`]
188
#[derive(Clone, Debug, thiserror::Error)]
189
#[non_exhaustive]
190
pub enum AddrParseError {
191
    /// Tried to parse an address with an unrecognized schema.
192
    #[error("Address schema {0:?} unrecognized")]
193
    UnrecognizedSchema(String),
194
    /// Tried to parse a non inet-address with no schema.
195
    #[error("Address did not look like internet, but had no address schema.")]
196
    NoSchema,
197
    /// Tried to parse an address as an AF_UNIX address, but failed.
198
    #[error("Invalid AF_UNIX address")]
199
    InvalidUnixAddress(#[source] Arc<IoError>),
200
    /// Tried to parse an address as a inet address, but failed.
201
    #[error("Invalid internet address")]
202
    InvalidInetAddress(#[from] std::net::AddrParseError),
203
}
204

            
205
impl From<IoError> for AddrParseError {
206
    fn from(e: IoError) -> Self {
207
        Self::InvalidUnixAddress(Arc::new(e))
208
    }
209
}
210

            
211
impl PartialEq for SocketAddr {
212
    /// Return true if two `SocketAddr`s are equal.
213
    ///
214
    /// For `Inet` addresses, delegates to `std::net::SocketAddr::eq`.
215
    ///
216
    /// For `Unix` addresses, treats two addresses as equal if any of the following is true:
217
    ///   - Both addresses have the same path.
218
    ///   - Both addresses are unnamed.
219
    ///   - (Linux only) Both addresses have the same abstract name.
220
    ///
221
    /// Addresses of different types are always unequal.
222
545
    fn eq(&self, other: &Self) -> bool {
223
545
        match (self, other) {
224
310
            (Self::Inet(l0), Self::Inet(r0)) => l0 == r0,
225
            #[cfg(unix)]
226
235
            (Self::Unix(l0), Self::Unix(r0)) => {
227
235
                // Sadly, std::os::unix::net::SocketAddr doesn't implement PartialEq.
228
235
                //
229
235
                // This requires us to make our own, and prevents us from providing Eq.
230
235
                if l0.is_unnamed() && r0.is_unnamed() {
231
2
                    return true;
232
233
                }
233
233
                if let (Some(a), Some(b)) = (l0.as_pathname(), r0.as_pathname()) {
234
233
                    return a == b;
235
                }
236
                #[cfg(any(target_os = "android", target_os = "linux"))]
237
                if let (Some(a), Some(b)) = (l0.as_abstract_name(), r0.as_abstract_name()) {
238
                    return a == b;
239
                }
240
                false
241
            }
242
            _ => false,
243
        }
244
545
    }
245
}
246

            
247
#[cfg(feature = "arbitrary")]
248
impl<'a> arbitrary::Arbitrary<'a> for SocketAddr {
249
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
250
        /// Simple enumeration to select an address type.
251
        #[allow(clippy::missing_docs_in_private_items)]
252
        #[derive(arbitrary::Arbitrary)]
253
        enum Kind {
254
            V4,
255
            V6,
256
            #[cfg(unix)]
257
            Unix,
258
            #[cfg(any(target_os = "android", target_os = "linux"))]
259
            UnixAbstract,
260
        }
261
        match u.arbitrary()? {
262
            Kind::V4 => Ok(SocketAddr::Inet(
263
                net::SocketAddrV4::new(u.arbitrary()?, u.arbitrary()?).into(),
264
            )),
265
            Kind::V6 => Ok(SocketAddr::Inet(
266
                net::SocketAddrV6::new(
267
                    u.arbitrary()?,
268
                    u.arbitrary()?,
269
                    u.arbitrary()?,
270
                    u.arbitrary()?,
271
                )
272
                .into(),
273
            )),
274
            #[cfg(unix)]
275
            Kind::Unix => {
276
                let pathname: std::ffi::OsString = u.arbitrary()?;
277
                Ok(SocketAddr::Unix(
278
                    unix::SocketAddr::from_pathname(pathname)
279
                        .map_err(|_| arbitrary::Error::IncorrectFormat)?,
280
                ))
281
            }
282
            #[cfg(any(target_os = "android", target_os = "linux"))]
283
            Kind::UnixAbstract => {
284
                use std::os::linux::net::SocketAddrExt as _;
285
                let name: &[u8] = u.arbitrary()?;
286
                Ok(SocketAddr::Unix(
287
                    unix::SocketAddr::from_abstract_name(name)
288
                        .map_err(|_| arbitrary::Error::IncorrectFormat)?,
289
                ))
290
            }
291
        }
292
    }
293
}
294

            
295
#[cfg(test)]
296
mod test {
297
    // @@ begin test lint list maintained by maint/add_warning @@
298
    #![allow(clippy::bool_assert_comparison)]
299
    #![allow(clippy::clone_on_copy)]
300
    #![allow(clippy::dbg_macro)]
301
    #![allow(clippy::mixed_attributes_style)]
302
    #![allow(clippy::print_stderr)]
303
    #![allow(clippy::print_stdout)]
304
    #![allow(clippy::single_char_pattern)]
305
    #![allow(clippy::unwrap_used)]
306
    #![allow(clippy::unchecked_duration_subtraction)]
307
    #![allow(clippy::useless_vec)]
308
    #![allow(clippy::needless_pass_by_value)]
309
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
310

            
311
    use super::AddrParseError;
312
    use crate::general;
313
    use assert_matches::assert_matches;
314
    #[cfg(unix)]
315
    use std::os::unix::net as unix;
316
    use std::{net, str::FromStr as _};
317

            
318
    /// Parse `s` as a `net::SocketAddr`, and build a `general::SocketAddr` from it.
319
    ///
320
    /// Testing only. Panics on error.
321
    fn from_inet(s: &str) -> general::SocketAddr {
322
        let a: net::SocketAddr = s.parse().unwrap();
323
        a.into()
324
    }
325

            
326
    #[test]
327
    fn ok_inet() {
328
        assert_eq!(
329
            from_inet("127.0.0.1:9999"),
330
            general::SocketAddr::from_str("127.0.0.1:9999").unwrap()
331
        );
332
        assert_eq!(
333
            from_inet("127.0.0.1:9999"),
334
            general::SocketAddr::from_str("inet:127.0.0.1:9999").unwrap()
335
        );
336

            
337
        assert_eq!(
338
            from_inet("[::1]:9999"),
339
            general::SocketAddr::from_str("[::1]:9999").unwrap()
340
        );
341
        assert_eq!(
342
            from_inet("[::1]:9999"),
343
            general::SocketAddr::from_str("inet:[::1]:9999").unwrap()
344
        );
345

            
346
        assert_ne!(
347
            general::SocketAddr::from_str("127.0.0.1:9999").unwrap(),
348
            general::SocketAddr::from_str("[::1]:9999").unwrap()
349
        );
350

            
351
        let ga1 = from_inet("127.0.0.1:9999");
352
        assert_eq!(ga1.display_lossy().to_string(), "inet:127.0.0.1:9999");
353
        assert_eq!(ga1.try_to_string().unwrap(), "inet:127.0.0.1:9999");
354

            
355
        let ga2 = from_inet("[::1]:9999");
356
        assert_eq!(ga2.display_lossy().to_string(), "inet:[::1]:9999");
357
        assert_eq!(ga2.try_to_string().unwrap(), "inet:[::1]:9999");
358
    }
359

            
360
    /// Treat `s` as a unix path, and build a `general::SocketAddr` from it.
361
    ///
362
    /// Testing only. Panics on error.
363
    #[cfg(unix)]
364
    fn from_pathname(s: impl AsRef<std::path::Path>) -> general::SocketAddr {
365
        let a = unix::SocketAddr::from_pathname(s).unwrap();
366
        a.into()
367
    }
368
    #[test]
369
    #[cfg(unix)]
370
    fn ok_unix() {
371
        assert_eq!(
372
            from_pathname("/some/path"),
373
            general::SocketAddr::from_str("unix:/some/path").unwrap()
374
        );
375
        assert_eq!(
376
            from_pathname("/another/path"),
377
            general::SocketAddr::from_str("unix:/another/path").unwrap()
378
        );
379
        assert_eq!(
380
            from_pathname("/path/with spaces"),
381
            general::SocketAddr::from_str("unix:/path/with spaces").unwrap()
382
        );
383
        assert_ne!(
384
            general::SocketAddr::from_str("unix:/some/path").unwrap(),
385
            general::SocketAddr::from_str("unix:/another/path").unwrap()
386
        );
387
        assert_eq!(
388
            from_pathname(""),
389
            general::SocketAddr::from_str("unix:").unwrap()
390
        );
391

            
392
        let ga1 = general::SocketAddr::from_str("unix:/some/path").unwrap();
393
        assert_eq!(ga1.display_lossy().to_string(), "unix:/some/path");
394
        assert_eq!(ga1.try_to_string().unwrap(), "unix:/some/path");
395

            
396
        let ga2 = general::SocketAddr::from_str("unix:/another/path").unwrap();
397
        assert_eq!(ga2.display_lossy().to_string(), "unix:/another/path");
398
        assert_eq!(ga2.try_to_string().unwrap(), "unix:/another/path");
399
    }
400

            
401
    #[test]
402
    fn parse_err_inet() {
403
        assert_matches!(
404
            "1234567890:999".parse::<general::SocketAddr>(),
405
            Err(AddrParseError::InvalidInetAddress(_))
406
        );
407
        assert_matches!(
408
            "1z".parse::<general::SocketAddr>(),
409
            Err(AddrParseError::InvalidInetAddress(_))
410
        );
411
        assert_matches!(
412
            "[[77".parse::<general::SocketAddr>(),
413
            Err(AddrParseError::InvalidInetAddress(_))
414
        );
415

            
416
        assert_matches!(
417
            "inet:fred:9999".parse::<general::SocketAddr>(),
418
            Err(AddrParseError::InvalidInetAddress(_))
419
        );
420

            
421
        assert_matches!(
422
            "inet:127.0.0.1".parse::<general::SocketAddr>(),
423
            Err(AddrParseError::InvalidInetAddress(_))
424
        );
425

            
426
        assert_matches!(
427
            "inet:[::1]".parse::<general::SocketAddr>(),
428
            Err(AddrParseError::InvalidInetAddress(_))
429
        );
430
    }
431

            
432
    #[test]
433
    fn parse_err_schemata() {
434
        assert_matches!(
435
            "fred".parse::<general::SocketAddr>(),
436
            Err(AddrParseError::NoSchema)
437
        );
438
        assert_matches!(
439
            "fred:".parse::<general::SocketAddr>(),
440
            Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
441
        );
442
        assert_matches!(
443
            "fred:hello".parse::<general::SocketAddr>(),
444
            Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
445
        );
446
    }
447

            
448
    #[test]
449
    #[cfg(unix)]
450
    fn display_unix_weird() {
451
        use std::ffi::OsStr;
452
        use std::os::unix::ffi::OsStrExt as _;
453

            
454
        let a1 = from_pathname(OsStr::from_bytes(&[255, 255, 255, 255]));
455
        assert!(a1.try_to_string().is_none());
456
        assert_eq!(a1.display_lossy().to_string(), "unix:���� [lossy]");
457

            
458
        let a2 = from_pathname("");
459
        assert_eq!(a2.try_to_string().unwrap(), "unix:");
460
        assert_eq!(a2.display_lossy().to_string(), "unix:");
461
    }
462

            
463
    #[test]
464
    #[cfg(not(unix))]
465
    fn parse_err_no_unix() {
466
        assert_matches!(
467
            "unix:".parse::<general::SocketAddr>(),
468
            Err(AddrParseError::InvalidUnixAddress(_))
469
        );
470
        assert_matches!(
471
            "unix:/any/path".parse::<general::SocketAddr>(),
472
            Err(AddrParseError::InvalidUnixAddress(_))
473
        );
474
    }
475
}