Skip to main content

arti_client/
address.rs

1//! Types and traits for converting objects to addresses which
2//! Tor can connect to.
3
4use crate::StreamPrefs;
5use crate::err::ErrorDetail;
6use std::fmt::Display;
7use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
8use std::str::FromStr;
9use thiserror::Error;
10use tor_basic_utils::StrExt;
11use tor_error::{ErrorKind, HasKind};
12
13#[cfg(feature = "onion-service-client")]
14use tor_hscrypto::pk::{HSID_ONION_SUFFIX, HsId};
15
16/// Fake plastic imitation of some of the `tor-hs*` functionality
17#[cfg(not(feature = "onion-service-client"))]
18pub(crate) mod hs_dummy {
19    use super::*;
20    use tor_error::internal;
21    use void::Void;
22
23    /// Parsed hidden service identity - uninhabited, since not supported
24    #[derive(Debug, Clone)]
25    pub(crate) struct HsId(pub(crate) Void);
26
27    impl PartialEq for HsId {
28        fn eq(&self, _other: &Self) -> bool {
29            void::unreachable(self.0)
30        }
31    }
32    impl Eq for HsId {}
33
34    /// Duplicates `tor-hscrypto::pk::HSID_ONION_SUFFIX`, ah well
35    pub(crate) const HSID_ONION_SUFFIX: &str = ".onion";
36
37    /// Must not be used other than for actual `.onion` addresses
38    impl FromStr for HsId {
39        type Err = ErrorDetail;
40
41        fn from_str(s: &str) -> Result<Self, Self::Err> {
42            if !s.ends_with(HSID_ONION_SUFFIX) {
43                return Err(internal!("non-.onion passed to dummy HsId::from_str").into());
44            }
45
46            Err(ErrorDetail::OnionAddressNotSupported)
47        }
48    }
49}
50#[cfg(not(feature = "onion-service-client"))]
51use hs_dummy::*;
52
53// ----------------------------------------------------------------------
54
55/// An object that can be converted to a [`TorAddr`] with a minimum of risk.
56///
57/// Typically, this trait will be implemented for a hostname or service name.
58///
59/// Don't implement this trait for IP addresses and similar types; instead,
60/// implement [`DangerouslyIntoTorAddr`] for those.  (The trouble with accepting
61/// IP addresses is that, in order to get an IP address, most programs will do a
62/// local hostname lookup, which will leak the target address to the DNS
63/// resolver. The `DangerouslyIntoTorAddr` trait provides a contract for careful
64/// programs to say, "I have gotten this IP address from somewhere safe."  This
65/// trait is for name-based addressing and similar, which _usually_ gets its
66/// addresses from a safer source.)
67///
68/// [*See also: the `TorAddr` documentation.*](TorAddr)
69///
70/// # Design note
71///
72/// We use a separate trait here, instead of using `Into<TorAddr>` or
73/// `TryInto<TorAddr>`, because `IntoTorAddr` implies additional guarantees
74/// relating to privacy risk.  The separate trait alerts users that something
75/// tricky is going on here, and encourages them to think twice before
76/// implementing `IntoTorAddr` for their own types.
77pub trait IntoTorAddr {
78    /// Try to make a [`TorAddr`] to represent connecting to this
79    /// address.
80    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError>;
81}
82
83/// An object that can be converted to a [`TorAddr`], but which it
84/// might be risky to get in the first place if you're hoping for
85/// anonymity.
86///
87/// For example, you can use this trait to convert a [`SocketAddr`]
88/// into a [`TorAddr`], and it's safe to do that conversion.  But
89/// where did you get the [`SocketAddr`] in the first place?  If it
90/// comes from a local DNS lookup, then you have leaked the address
91/// you were resolving to your DNS resolver, and probably your ISP.
92///
93/// [*See also: the `TorAddr` documentation.*](TorAddr)
94pub trait DangerouslyIntoTorAddr {
95    /// Try to make a [`TorAddr`] to represent connecting to `self`.
96    ///
97    /// By calling this function, the caller asserts that `self` was
98    /// obtained from some secure, private mechanism, and **not** from a local
99    /// DNS lookup or something similar.
100    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError>;
101}
102
103/// An address object that you can connect to over the Tor network.
104///
105/// When you're making a connection with Tor, you shouldn't do your DNS
106/// lookups locally: that would leak your target address to your DNS server.
107/// Instead, it's better to use a combination of a hostname and a port
108/// directly.
109///
110/// The preferred way to create a `TorAddr` is via the [`IntoTorAddr`] trait,
111/// using a hostname and a port (or a string containing a hostname and a
112/// port).  It's also okay to use an IP and Port there, but only if they come
113/// from some source _other than_ a local DNS lookup.
114///
115/// In order to discourage local hostname lookups, the functions that
116/// construct a [`TorAddr`] from [`IpAddr`], [`SocketAddr`], and so
117/// forth are labeled as "dangerous".
118///
119/// # Examples
120///
121/// Making a `TorAddr` from various "safe" sources:
122///
123/// ```rust
124/// # use anyhow::Result;
125/// # fn main() -> Result<()> {
126/// use arti_client::IntoTorAddr;
127///
128/// let example_from_tuple = ("example.com", 80).into_tor_addr()?;
129/// let example_from_string = "example.com:80".into_tor_addr()?;
130///
131/// assert_eq!(example_from_tuple, example_from_string);
132/// # Ok(())
133/// # }
134/// ```
135///
136/// Making a `TorAddr` from an IP address and port:
137///
138/// > **Warning:** This example is only safe because we're not doing a DNS lookup; rather, the
139/// > intent is to connect to a hardcoded IP address.
140/// > If you're using [`DangerouslyIntoTorAddr`], pay careful attention to where your IP addresses
141/// > are coming from, and whether there's a risk of information leakage.
142///
143/// ```rust
144/// # use anyhow::Result;
145/// # fn main() -> Result<()> {
146/// use arti_client::DangerouslyIntoTorAddr;
147/// use std::net::{IpAddr, SocketAddr};
148///
149/// let quad_one_dns: SocketAddr = "1.1.1.1:53".parse()?;
150/// let addr_from_socketaddr = quad_one_dns.into_tor_addr_dangerously()?;
151///
152/// let quad_one_ip: IpAddr = "1.1.1.1".parse()?;
153/// let addr_from_tuple = (quad_one_ip, 53).into_tor_addr_dangerously()?;
154///
155/// assert_eq!(addr_from_socketaddr, addr_from_tuple);
156/// # Ok(())
157/// # }
158/// ```
159#[derive(Debug, Clone, Eq, PartialEq)]
160pub struct TorAddr {
161    /// The target host.
162    host: Host,
163    /// The target port number.
164    port: u16,
165}
166
167/// How to make a stream to this `TorAddr`?
168///
169/// This is a separate type, returned from `address.rs` to `client.rs`,
170/// so that we can test our "how to make a connection" logic and policy,
171/// in isolation, without a whole Tor client.
172#[derive(Debug, PartialEq, Eq)]
173pub(crate) enum StreamInstructions {
174    /// Create an exit circuit suitable for port, and then make a stream to `hostname`
175    Exit {
176        /// Hostname
177        hostname: String,
178        /// Port
179        port: u16,
180    },
181    /// Create a hidden service connection to hsid, and then make a stream to `hostname`
182    ///
183    /// `HsId`, and therefore this variant, is uninhabited, unless the feature is enabled
184    Hs {
185        /// The target hidden service
186        hsid: HsId,
187        /// The hostname (used for subdomains, sent to the peer)
188        hostname: String,
189        /// Port
190        port: u16,
191    },
192}
193
194/// How to resolve this Tor host address into IP address(es)
195#[derive(PartialEq, Eq, Debug)]
196pub(crate) enum ResolveInstructions {
197    /// Create an exit circuit without port restrictions, and ask the exit
198    Exit(String),
199    /// Simply return this
200    Return(Vec<IpAddr>),
201}
202
203impl TorAddr {
204    /// Construct a TorAddr from its constituent parts, rejecting it if the
205    /// port is zero.
206    fn new(host: Host, port: u16) -> Result<Self, TorAddrError> {
207        if port == 0 {
208            Err(TorAddrError::BadPort)
209        } else {
210            Ok(TorAddr { host, port })
211        }
212    }
213
214    /// Construct a `TorAddr` from any object that implements
215    /// [`IntoTorAddr`].
216    pub fn from<A: IntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
217        addr.into_tor_addr()
218    }
219    /// Construct a `TorAddr` from any object that implements
220    /// [`DangerouslyIntoTorAddr`].
221    ///
222    /// See [`DangerouslyIntoTorAddr`] for an explanation of why the
223    /// style of programming supported by this function is dangerous
224    /// to use.
225    pub fn dangerously_from<A: DangerouslyIntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
226        addr.into_tor_addr_dangerously()
227    }
228
229    /// Return true if this is an IP address (rather than a hostname).
230    pub fn is_ip_address(&self) -> bool {
231        matches!(&self.host, Host::Ip(_))
232    }
233
234    /// If this TorAddr is an explicit IP address, return a reference to that [`IpAddr`].
235    pub fn as_ip_address(&self) -> Option<&IpAddr> {
236        match &self.host {
237            Host::Ip(a) => Some(a),
238            _ => None,
239        }
240    }
241
242    /// Get instructions for how to make a stream to this address
243    pub(crate) fn into_stream_instructions(
244        self,
245        cfg: &crate::config::ClientAddrConfig,
246        prefs: &StreamPrefs,
247    ) -> Result<StreamInstructions, ErrorDetail> {
248        self.enforce_config(cfg, prefs)?;
249
250        let port = self.port;
251        Ok(match self.host {
252            Host::Hostname(hostname) => StreamInstructions::Exit { hostname, port },
253            Host::Ip(ip) => StreamInstructions::Exit {
254                hostname: ip.to_string(),
255                port,
256            },
257            Host::Onion(onion) => {
258                // The HS is identified by the last two domain name components
259                let rhs = onion
260                    .rmatch_indices('.')
261                    .nth(1)
262                    .map(|(i, _)| i + 1)
263                    .unwrap_or(0);
264                let rhs = onion
265                    .get(rhs..)
266                    .expect("character index was not a valid index!?");
267                let hsid = rhs.parse()?;
268                StreamInstructions::Hs {
269                    hsid,
270                    port,
271                    hostname: onion,
272                }
273            }
274        })
275    }
276
277    /// Get instructions for how to make a stream to this address
278    pub(crate) fn into_resolve_instructions(
279        self,
280        cfg: &crate::config::ClientAddrConfig,
281        prefs: &StreamPrefs,
282    ) -> Result<ResolveInstructions, ErrorDetail> {
283        // We defer enforcing the config until we see if this is a .onion,
284        // in which case it's always doomed and we want to return *our* error,
285        // not any problem with the configuration or preferences.
286        // But we must *calculate* the error now because instructions consumes self.
287        let enforce_config_result = self.enforce_config(cfg, prefs);
288
289        // This IEFE is so that any use of `return` doesn't bypass
290        // checking the enforce_config result
291        let instructions = (move || {
292            Ok(match self.host {
293                Host::Hostname(hostname) => ResolveInstructions::Exit(hostname),
294                Host::Ip(ip) => ResolveInstructions::Return(vec![ip]),
295                Host::Onion(_) => return Err(ErrorDetail::OnionAddressResolveRequest),
296            })
297        })()?;
298
299        let () = enforce_config_result?;
300
301        Ok(instructions)
302    }
303
304    /// Return true if `addr` is a globally reachable address.
305    ///
306    /// Used for deciding if this [`TorAddr`] is an acceptable BEGIN target
307    /// (typically, the target of a BEGIN must be globally reachable).
308    ///
309    /// See [`is_globally_reachable_unicast`].
310    fn is_globally_reachable_unicast(&self) -> bool {
311        self.host.is_globally_reachable_unicast()
312    }
313
314    /// Give an error if this address doesn't conform to the rules set in
315    /// `cfg`.
316    fn enforce_config(
317        &self,
318        cfg: &crate::config::ClientAddrConfig,
319        #[allow(unused_variables)] // will only be used in certain configurations
320        prefs: &StreamPrefs,
321    ) -> Result<(), ErrorDetail> {
322        let allow_local_addrs = prefs
323            .connect_to_local_addrs
324            .as_bool()
325            .unwrap_or(cfg.allow_local_addrs);
326
327        if !allow_local_addrs && !self.is_globally_reachable_unicast() {
328            return Err(ErrorDetail::LocalAddress);
329        }
330
331        if let Host::Hostname(addr) = &self.host {
332            if !is_valid_hostname(addr) {
333                // This ought not to occur, because it violates Host's invariant
334                return Err(ErrorDetail::InvalidHostname);
335            }
336            if addr.ends_with_ignore_ascii_case(HSID_ONION_SUFFIX) {
337                // This ought not to occur, because it violates Host's invariant
338                return Err(ErrorDetail::OnionAddressNotSupported);
339            }
340        }
341
342        if let Host::Onion(_name) = &self.host {
343            cfg_if::cfg_if! {
344                if #[cfg(feature = "onion-service-client")] {
345                    if !prefs.connect_to_onion_services.as_bool().unwrap_or(cfg.allow_onion_addrs) {
346                        return Err(ErrorDetail::OnionAddressDisabled);
347                    }
348                } else {
349                    return Err(ErrorDetail::OnionAddressNotSupported);
350                }
351            }
352        }
353
354        Ok(())
355    }
356}
357
358impl std::fmt::Display for TorAddr {
359    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360        match self.host {
361            Host::Ip(IpAddr::V6(addr)) => write!(f, "[{}]:{}", addr, self.port),
362            _ => write!(f, "{}:{}", self.host, self.port),
363        }
364    }
365}
366
367/// An error created while making or using a [`TorAddr`].
368//
369// NOTE: Unlike ErrorDetail, this is a `pub` enum: Do not make breaking changes
370// to it, or expose lower-level errors in it, without careful consideration!
371#[derive(Debug, Error, Clone, Eq, PartialEq)]
372#[non_exhaustive]
373pub enum TorAddrError {
374    /// Tried to parse a string that can never be interpreted as a valid host.
375    #[error("String can never be a valid hostname")]
376    InvalidHostname,
377    /// Tried to parse a string as an `address:port`, but it had no port.
378    #[error("No port found in string")]
379    NoPort,
380    /// Tried to parse a port that wasn't a valid nonzero `u16`.
381    #[error("Could not parse port")]
382    BadPort,
383}
384
385impl HasKind for TorAddrError {
386    fn kind(&self) -> ErrorKind {
387        use ErrorKind as EK;
388        use TorAddrError as TAE;
389
390        match self {
391            TAE::InvalidHostname => EK::InvalidStreamTarget,
392            TAE::NoPort => EK::InvalidStreamTarget,
393            TAE::BadPort => EK::InvalidStreamTarget,
394        }
395    }
396}
397
398/// A host that Tor can connect to: either a hostname or an IP address.
399//
400// We use `String` in here, and pass that directly to (for example)
401// `HsId::from_str`, or `begin_stream`.
402// In theory we could use a couple of newtypes or something, but
403//  * The stringly-typed `HsId::from_str` call (on a string known to end `.onion`)
404//    appears precisely in `into_stream_instructions` which knows what it's doing;
405//  * The stringly-typed .onion domain name must be passed in the
406//    StreamInstructions so that we can send it to the HS for its vhosting.
407#[derive(Clone, Debug, Eq, PartialEq)]
408enum Host {
409    /// A hostname.
410    ///
411    /// This variant should never be used if the `Ip`
412    /// variant could be used instead.
413    /// Ie, it must not be a stringified IP address.
414    ///
415    /// Likewise, this variant must *not* be used for a `.onion` address.
416    /// Even if we have `.onion` support compiled out, we use the `Onion` variant for that.
417    ///
418    /// But, this variant might *not* be on the public internet.
419    /// For example, it might be `localhost`.
420    Hostname(String),
421    /// An IP address.
422    Ip(IpAddr),
423    /// The address of a hidden service (`.onion` service).
424    ///
425    /// We haven't validated that the base32 makes any kind of sense, yet.
426    /// We do that when we try to connect.
427    Onion(String),
428}
429
430impl FromStr for Host {
431    type Err = TorAddrError;
432    fn from_str(s: &str) -> Result<Host, TorAddrError> {
433        if s.ends_with_ignore_ascii_case(".onion") && is_valid_hostname(s) {
434            Ok(Host::Onion(s.to_owned()))
435        } else if let Ok(ip_addr) = s.parse() {
436            Ok(Host::Ip(ip_addr))
437        } else if is_valid_hostname(s) {
438            // TODO(nickm): we might someday want to reject some kinds of bad
439            // hostnames here, rather than when we're about to connect to them.
440            // But that would be an API break, and maybe not what people want.
441            // Maybe instead we should have a method to check whether a hostname
442            // is "bad"? Not sure; we'll need to decide the right behavior here.
443            Ok(Host::Hostname(s.to_owned()))
444        } else {
445            Err(TorAddrError::InvalidHostname)
446        }
447    }
448}
449
450impl Host {
451    /// Return true if this address is one that is not "internal": that is,
452    /// if `addr` is a globally reachable unicast address.
453    fn is_globally_reachable_unicast(&self) -> bool {
454        match self {
455            Host::Hostname(name) => !name.eq_ignore_ascii_case("localhost"),
456            Host::Ip(ip) => is_globally_reachable_unicast(*ip),
457            Host::Onion(_) => true,
458        }
459    }
460}
461
462/// Return true if `addr` is a globally reachable unicast address.
463///
464/// Returns false if `addr` is any of the following:
465///
466///   * the loopback address (127.0.0.1/8, ::1). See RFC1122, RFC4291
467///   * a private address, as defined in RFC1918
468///   * unspecified (0.0.0.0, ::)
469///   * part of the Shared Address Space defined in RFC6598 (100.64.0.0/10)
470///   * a unique local address (fc00::/7). See RFC4193
471///   * a unicast address with link-local scope, as defined in RFC4291
472///
473/// Used for deciding whether an address is a valid BEGIN target.
474/// Additionally, we use this to determine whether to allow a reverse DNS lookup
475/// (ordinarily, reverse lookups for non-globally reachable addresses are rejected),
476/// and to filter out any non-globally reachable addresses from the exit-provided
477/// RESOLVED responses.
478///
479/// False negatives are allowed, but false positives are not
480/// (to avoid unintentionally opening connections to local services,
481/// either on the exit, or on the client itself).
482///
483/// Semantically, this function is roughly equivalent to checking
484/// `(IpAddr::is_global() && !IpAddr::is_broadcast() && !IpAddr::is_multicast())`,
485/// with the only difference being that our implementation of the `is_global()` check
486/// does not cover
487///
488///   * addresses reserved for benchmarking (RFC2544, RFC5180)
489///   * addresses reserved for documentation (RFC5737, RFC3849, RFC9637)
490///   * v4 addresses reserved by IANA for future use (RFC1112)
491///   * v6 discard-only address block (`100::/64`)
492///   * v6 IETF special assignments of `2001::/23`
493///   * 6to4 (`2002::/16`) – it's not explicitly documented as globally reachable,
494///     IANA says N/A.
495///   * segment routing (SRv6) SIDs (`5f00::/16`)
496///
497// Note(gabi): for the sake of completeness, we may want to extend this function to
498// cover the ranges mentioned above too, because in theory, these addresses
499// shouldn't be reachable anyway.
500//
501// TODO: we may want to rewrite this function using IpAddr::is_global() once
502// that becomes stable.
503pub(crate) fn is_globally_reachable_unicast(addr: IpAddr) -> bool {
504    // This ensures we handle IPv4-mapped addresses correctly
505    let addr = addr.to_canonical();
506    // TODO: use is_global once it's stable, perhaps.
507    // NOTE: Contrast this with is_sufficiently_private in tor-hsproxy,
508    // which has a different purpose. Also see #1159.
509    // The purpose of _this_ test is to find addresses that cannot
510    // meaningfully be connected to over Tor, and that the exit
511    // will not accept.
512    //
513    // TODO: we may want to extend this to cover more non-routable ranges
514    // (for example, the reserved documentation prefixes)
515    !(match addr {
516        IpAddr::V4(v4) => {
517            v4.is_loopback() // RFC1122 (127.0.0.0/8)
518                || v4.is_private() // RFC1918
519                || v4.is_unspecified() // 0.0.0.0
520                || v4.is_link_local() // RFC3927 (169.254.0.0/16)
521                || is_shared(v4) // RFC6598
522                || v4.is_broadcast() // RFC919
523                || v4.is_multicast() // RFC6771
524        }
525        IpAddr::V6(v6) => {
526            v6.is_loopback() // RFC4291 (::1)
527                || v6.is_unspecified() // RFC4291 (::)
528                || v6.is_unique_local() // RFC4193 (fc00::/7)
529                || v6.is_unicast_link_local() // RFC4291 (fe80::/10)
530                || v6.is_multicast() // RFC4291
531        }
532    })
533}
534/// Returns [`true`] if this address is part of the Shared Address Space defined in
535/// [IETF RFC 6598] (`100.64.0.0/10`).
536///
537/// [IETF RFC 6598]: https://tools.ietf.org/html/rfc6598
538///
539// TODO: use IPv4::is_shared() when it becomes stable.
540// See <https://github.com/rust-lang/rust/issues/137259>
541fn is_shared(addr: Ipv4Addr) -> bool {
542    addr.octets()[0] == 100 && (addr.octets()[1] & 0b1100_0000 == 0b0100_0000)
543}
544
545impl std::fmt::Display for Host {
546    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547        match self {
548            Host::Hostname(s) => Display::fmt(s, f),
549            Host::Ip(ip) => Display::fmt(ip, f),
550            Host::Onion(onion) => Display::fmt(onion, f),
551        }
552    }
553}
554
555impl IntoTorAddr for TorAddr {
556    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
557        Ok(self)
558    }
559}
560
561impl<A: IntoTorAddr + Clone> IntoTorAddr for &A {
562    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
563        self.clone().into_tor_addr()
564    }
565}
566
567impl IntoTorAddr for &str {
568    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
569        if let Ok(sa) = SocketAddr::from_str(self) {
570            TorAddr::new(Host::Ip(sa.ip()), sa.port())
571        } else {
572            let (host, port) = self.rsplit_once(':').ok_or(TorAddrError::NoPort)?;
573            let host = host.parse()?;
574            let port = port.parse().map_err(|_| TorAddrError::BadPort)?;
575            TorAddr::new(host, port)
576        }
577    }
578}
579
580impl IntoTorAddr for String {
581    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
582        self.as_str().into_tor_addr()
583    }
584}
585
586impl FromStr for TorAddr {
587    type Err = TorAddrError;
588    fn from_str(s: &str) -> Result<Self, TorAddrError> {
589        s.into_tor_addr()
590    }
591}
592
593impl IntoTorAddr for (&str, u16) {
594    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
595        let (host, port) = self;
596        let host = host.parse()?;
597        TorAddr::new(host, port)
598    }
599}
600
601impl IntoTorAddr for (String, u16) {
602    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
603        let (host, port) = self;
604        (host.as_str(), port).into_tor_addr()
605    }
606}
607
608impl<T: DangerouslyIntoTorAddr + Clone> DangerouslyIntoTorAddr for &T {
609    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
610        self.clone().into_tor_addr_dangerously()
611    }
612}
613
614impl DangerouslyIntoTorAddr for (IpAddr, u16) {
615    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
616        let (addr, port) = self;
617        TorAddr::new(Host::Ip(addr), port)
618    }
619}
620
621impl DangerouslyIntoTorAddr for (Ipv4Addr, u16) {
622    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
623        let (addr, port) = self;
624        TorAddr::new(Host::Ip(addr.into()), port)
625    }
626}
627
628impl DangerouslyIntoTorAddr for (Ipv6Addr, u16) {
629    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
630        let (addr, port) = self;
631        TorAddr::new(Host::Ip(addr.into()), port)
632    }
633}
634
635impl DangerouslyIntoTorAddr for SocketAddr {
636    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
637        let (addr, port) = (self.ip(), self.port());
638        (addr, port).into_tor_addr_dangerously()
639    }
640}
641
642impl DangerouslyIntoTorAddr for SocketAddrV4 {
643    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
644        let (addr, port) = (self.ip(), self.port());
645        (*addr, port).into_tor_addr_dangerously()
646    }
647}
648
649impl DangerouslyIntoTorAddr for SocketAddrV6 {
650    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
651        let (addr, port) = (self.ip(), self.port());
652        (*addr, port).into_tor_addr_dangerously()
653    }
654}
655
656/// Check whether `hostname` is a valid hostname or not.
657///
658/// (Note that IPv6 addresses don't follow these rules.)
659fn is_valid_hostname(hostname: &str) -> bool {
660    hostname_validator::is_valid(hostname)
661}
662
663#[cfg(test)]
664mod test {
665    // @@ begin test lint list maintained by maint/add_warning @@
666    #![allow(clippy::bool_assert_comparison)]
667    #![allow(clippy::clone_on_copy)]
668    #![allow(clippy::dbg_macro)]
669    #![allow(clippy::mixed_attributes_style)]
670    #![allow(clippy::print_stderr)]
671    #![allow(clippy::print_stdout)]
672    #![allow(clippy::single_char_pattern)]
673    #![allow(clippy::unwrap_used)]
674    #![allow(clippy::unchecked_time_subtraction)]
675    #![allow(clippy::useless_vec)]
676    #![allow(clippy::needless_pass_by_value)]
677    #![allow(clippy::string_slice)] // See arti#2571
678    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
679    use super::*;
680
681    #[test]
682    fn test_error_kind() {
683        use tor_error::ErrorKind as EK;
684
685        assert_eq!(
686            TorAddrError::InvalidHostname.kind(),
687            EK::InvalidStreamTarget
688        );
689        assert_eq!(TorAddrError::NoPort.kind(), EK::InvalidStreamTarget);
690        assert_eq!(TorAddrError::BadPort.kind(), EK::InvalidStreamTarget);
691    }
692
693    /// Make a `StreamPrefs` with `.onion` enabled, if cfg-enabled
694    fn mk_stream_prefs() -> StreamPrefs {
695        let prefs = crate::StreamPrefs::default();
696
697        #[cfg(feature = "onion-service-client")]
698        let prefs = {
699            let mut prefs = prefs;
700            prefs.connect_to_onion_services(tor_config::BoolOrAuto::Explicit(true));
701            prefs
702        };
703
704        prefs
705    }
706
707    #[test]
708    fn validate_hostname() {
709        // Valid hostname tests
710        assert!(is_valid_hostname("torproject.org"));
711        assert!(is_valid_hostname("Tor-Project.org"));
712        assert!(is_valid_hostname("example.onion"));
713        assert!(is_valid_hostname("some.example.onion"));
714
715        // Invalid hostname tests
716        assert!(!is_valid_hostname("-torproject.org"));
717        assert!(!is_valid_hostname("_torproject.org"));
718        assert!(!is_valid_hostname("tor_project1.org"));
719        assert!(!is_valid_hostname("iwanna$money.org"));
720    }
721
722    #[test]
723    fn validate_addr() {
724        use crate::err::ErrorDetail;
725        fn val<A: IntoTorAddr>(addr: A) -> Result<TorAddr, ErrorDetail> {
726            let toraddr = addr.into_tor_addr()?;
727            toraddr.enforce_config(&Default::default(), &mk_stream_prefs())?;
728            Ok(toraddr)
729        }
730
731        assert!(val("[2001:db8::42]:20").is_ok());
732        assert!(val(("2001:db8::42", 20)).is_ok());
733        assert!(val(("198.151.100.42", 443)).is_ok());
734        assert!(val("198.151.100.42:443").is_ok());
735        assert!(val("www.torproject.org:443").is_ok());
736        assert!(val(("www.torproject.org", 443)).is_ok());
737
738        // When HS disabled, tested elsewhere, see: stream_instructions, prefs_onion_services
739        #[cfg(feature = "onion-service-client")]
740        {
741            assert!(val("example.onion:80").is_ok());
742            assert!(val(("example.onion", 80)).is_ok());
743
744            match val("eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443") {
745                Ok(TorAddr {
746                    host: Host::Onion(_),
747                    ..
748                }) => {}
749                x => panic!("{x:?}"),
750            }
751        }
752
753        assert!(matches!(
754            val("-foobar.net:443"),
755            Err(ErrorDetail::InvalidHostname)
756        ));
757        assert!(matches!(
758            val("www.torproject.org"),
759            Err(ErrorDetail::Address(TorAddrError::NoPort))
760        ));
761
762        assert!(matches!(
763            val("192.168.0.1:80"),
764            Err(ErrorDetail::LocalAddress)
765        ));
766        assert!(matches!(
767            val(TorAddr::new(Host::Hostname("foo@bar".to_owned()), 553).unwrap()),
768            Err(ErrorDetail::InvalidHostname)
769        ));
770        assert!(matches!(
771            val(TorAddr::new(Host::Hostname("foo.onion".to_owned()), 80).unwrap()),
772            Err(ErrorDetail::OnionAddressNotSupported)
773        ));
774    }
775
776    #[test]
777    fn local_addrs() {
778        fn is_local_hostname(s: &str) -> bool {
779            let h: Host = s.parse().unwrap();
780            !h.is_globally_reachable_unicast()
781        }
782
783        assert!(is_local_hostname("localhost"));
784        assert!(is_local_hostname("loCALHOST"));
785        assert!(is_local_hostname("127.0.0.1"));
786        assert!(is_local_hostname("::1"));
787        assert!(is_local_hostname("::ffff:127.0.0.1"));
788        assert!(is_local_hostname("192.168.0.1"));
789        assert!(is_local_hostname("0.0.0.0"));
790        assert!(is_local_hostname("::"));
791        assert!(is_local_hostname("100.64.0.1"));
792        assert!(is_local_hostname("fc00::"));
793        assert!(is_local_hostname("fe80::"));
794        assert!(is_local_hostname("255.255.255.255"));
795        assert!(is_local_hostname("224.1.1.1"));
796        assert!(is_local_hostname("ff00::"));
797
798        assert!(!is_local_hostname("www.example.com"));
799    }
800
801    #[test]
802    fn is_ip_address() {
803        fn ip(s: &str) -> bool {
804            TorAddr::from(s).unwrap().is_ip_address()
805        }
806
807        assert!(ip("192.168.0.1:80"));
808        assert!(ip("[::1]:80"));
809        assert!(ip("[2001:db8::42]:65535"));
810        assert!(!ip("example.com:80"));
811        assert!(!ip("example.onion:80"));
812    }
813
814    #[test]
815    fn stream_instructions() {
816        use StreamInstructions as SI;
817
818        fn sap(s: &str) -> Result<StreamInstructions, ErrorDetail> {
819            TorAddr::from(s)
820                .unwrap()
821                .into_stream_instructions(&Default::default(), &mk_stream_prefs())
822        }
823
824        assert_eq!(
825            sap("[2001:db8::42]:9001").unwrap(),
826            SI::Exit {
827                hostname: "2001:db8::42".to_owned(),
828                port: 9001
829            },
830        );
831        assert_eq!(
832            sap("example.com:80").unwrap(),
833            SI::Exit {
834                hostname: "example.com".to_owned(),
835                port: 80
836            },
837        );
838
839        {
840            let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
841            let onion = format!("sss1234.www.{}.onion", b32);
842            let got = sap(&format!("{}:443", onion));
843
844            #[cfg(feature = "onion-service-client")]
845            assert_eq!(
846                got.unwrap(),
847                SI::Hs {
848                    hsid: format!("{}.onion", b32).parse().unwrap(),
849                    hostname: onion,
850                    port: 443,
851                }
852            );
853
854            #[cfg(not(feature = "onion-service-client"))]
855            assert!(matches!(got, Err(ErrorDetail::OnionAddressNotSupported)));
856        }
857    }
858
859    #[test]
860    fn resolve_instructions() {
861        use ResolveInstructions as RI;
862
863        fn sap(s: &str, prefs: &StreamPrefs) -> Result<ResolveInstructions, ErrorDetail> {
864            TorAddr::from(s)
865                .unwrap()
866                .into_resolve_instructions(&Default::default(), prefs)
867        }
868
869        let default_prefs = Default::default();
870        assert_eq!(
871            sap("[2001:db8::42]:9001", &default_prefs).unwrap(),
872            RI::Return(vec!["2001:db8::42".parse().unwrap()]),
873        );
874        assert_eq!(
875            sap("example.com:80", &default_prefs).unwrap(),
876            RI::Exit("example.com".to_owned()),
877        );
878        assert!(matches!(
879            sap("example.onion:80", &default_prefs),
880            Err(ErrorDetail::OnionAddressResolveRequest),
881        ));
882        assert!(matches!(
883            sap("localhost:80", &default_prefs),
884            Err(ErrorDetail::LocalAddress),
885        ));
886        assert!(matches!(
887            sap("127.0.0.1:80", &default_prefs),
888            Err(ErrorDetail::LocalAddress),
889        ));
890
891        let mut connect_to_local_addrs = StreamPrefs::new();
892        connect_to_local_addrs.connect_to_local_addrs(tor_config::BoolOrAuto::Explicit(true));
893
894        assert_eq!(
895            sap("localhost:80", &connect_to_local_addrs).unwrap(),
896            RI::Exit("localhost".to_owned()),
897        );
898        assert_eq!(
899            sap("127.0.0.1:80", &connect_to_local_addrs).unwrap(),
900            RI::Return(vec!["127.0.0.1".parse().unwrap()]),
901        );
902    }
903
904    #[test]
905    fn bad_ports() {
906        assert_eq!(
907            TorAddr::from("www.example.com:squirrel"),
908            Err(TorAddrError::BadPort)
909        );
910        assert_eq!(
911            TorAddr::from("www.example.com:0"),
912            Err(TorAddrError::BadPort)
913        );
914    }
915
916    #[test]
917    fn prefs_onion_services() {
918        use crate::err::ErrorDetailDiscriminants;
919        use ErrorDetailDiscriminants as EDD;
920        use ErrorKind as EK;
921        use tor_error::{ErrorKind, HasKind as _};
922
923        #[allow(clippy::redundant_closure)] // for symmetry with prefs_of, below, and clarity
924        let prefs_def = || StreamPrefs::default();
925
926        let addr: TorAddr = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443"
927            .parse()
928            .unwrap();
929
930        fn map(
931            got: Result<impl Sized, ErrorDetail>,
932        ) -> Result<(), (ErrorDetailDiscriminants, ErrorKind)> {
933            got.map(|_| ())
934                .map_err(|e| (ErrorDetailDiscriminants::from(&e), e.kind()))
935        }
936
937        let check_stream = |prefs, expected| {
938            let got = addr
939                .clone()
940                .into_stream_instructions(&Default::default(), &prefs);
941            assert_eq!(map(got), expected, "{prefs:?}");
942        };
943        let check_resolve = |prefs| {
944            let got = addr
945                .clone()
946                .into_resolve_instructions(&Default::default(), &prefs);
947            // This should be OnionAddressResolveRequest no matter if .onion is compiled in or enabled.
948            // Since compiling it in, or enabling it, won't help.
949            let expected = Err((EDD::OnionAddressResolveRequest, EK::NotImplemented));
950            assert_eq!(map(got), expected, "{prefs:?}");
951        };
952
953        cfg_if::cfg_if! {
954            if #[cfg(feature = "onion-service-client")] {
955                use tor_config::BoolOrAuto as B;
956                let prefs_of = |yn| {
957                    let mut prefs = StreamPrefs::default();
958                    prefs.connect_to_onion_services(yn);
959                    prefs
960                };
961                check_stream(prefs_def(), Ok(()));
962                check_stream(prefs_of(B::Auto), Ok(()));
963                check_stream(prefs_of(B::Explicit(true)), Ok(()));
964                check_stream(prefs_of(B::Explicit(false)), Err((EDD::OnionAddressDisabled, EK::ForbiddenStreamTarget)));
965
966                check_resolve(prefs_def());
967                check_resolve(prefs_of(B::Auto));
968                check_resolve(prefs_of(B::Explicit(true)));
969                check_resolve(prefs_of(B::Explicit(false)));
970            } else {
971                check_stream(prefs_def(), Err((EDD::OnionAddressNotSupported, EK::FeatureDisabled)));
972
973                check_resolve(prefs_def());
974            }
975        }
976    }
977
978    #[test]
979    fn convert_safe() {
980        fn check<A: IntoTorAddr>(a: A, s: &str) {
981            let a1 = TorAddr::from(a).unwrap();
982            let a2 = s.parse().unwrap();
983            assert_eq!(a1, a2);
984            assert_eq!(&a1.to_string(), s);
985        }
986
987        check(("www.example.com", 8000), "www.example.com:8000");
988        check(
989            TorAddr::from(("www.example.com", 8000)).unwrap(),
990            "www.example.com:8000",
991        );
992        check(
993            TorAddr::from(("www.example.com", 8000)).unwrap(),
994            "www.example.com:8000",
995        );
996        let addr = "[2001:db8::0042]:9001".to_owned();
997        check(&addr, "[2001:db8::42]:9001");
998        check(addr, "[2001:db8::42]:9001");
999        check(("2001:db8::0042".to_owned(), 9001), "[2001:db8::42]:9001");
1000        check(("example.onion", 80), "example.onion:80");
1001    }
1002
1003    #[test]
1004    fn convert_dangerous() {
1005        fn check<A: DangerouslyIntoTorAddr>(a: A, s: &str) {
1006            let a1 = TorAddr::dangerously_from(a).unwrap();
1007            let a2 = TorAddr::from(s).unwrap();
1008            assert_eq!(a1, a2);
1009            assert_eq!(&a1.to_string(), s);
1010        }
1011
1012        let ip: IpAddr = "203.0.133.6".parse().unwrap();
1013        let ip4: Ipv4Addr = "203.0.133.7".parse().unwrap();
1014        let ip6: Ipv6Addr = "2001:db8::42".parse().unwrap();
1015        let sa: SocketAddr = "203.0.133.8:80".parse().unwrap();
1016        let sa4: SocketAddrV4 = "203.0.133.8:81".parse().unwrap();
1017        let sa6: SocketAddrV6 = "[2001:db8::43]:82".parse().unwrap();
1018
1019        // This tests impl DangerouslyIntoTorAddr for &T
1020        #[allow(clippy::needless_borrow)]
1021        #[allow(clippy::needless_borrows_for_generic_args)]
1022        check(&(ip, 443), "203.0.133.6:443");
1023        check((ip, 443), "203.0.133.6:443");
1024        check((ip4, 444), "203.0.133.7:444");
1025        check((ip6, 445), "[2001:db8::42]:445");
1026        check(sa, "203.0.133.8:80");
1027        check(sa4, "203.0.133.8:81");
1028        check(sa6, "[2001:db8::43]:82");
1029    }
1030}