arti_client/
address.rs

1//! Types and traits for converting objects to addresses which
2//! Tor can connect to.
3
4use crate::err::ErrorDetail;
5use crate::StreamPrefs;
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, HSID_ONION_SUFFIX};
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    /// Get instructions for how to make a stream to this address
235    pub(crate) fn into_stream_instructions(
236        self,
237        cfg: &crate::config::ClientAddrConfig,
238        prefs: &StreamPrefs,
239    ) -> Result<StreamInstructions, ErrorDetail> {
240        self.enforce_config(cfg, prefs)?;
241
242        let port = self.port;
243        Ok(match self.host {
244            Host::Hostname(hostname) => StreamInstructions::Exit { hostname, port },
245            Host::Ip(ip) => StreamInstructions::Exit {
246                hostname: ip.to_string(),
247                port,
248            },
249            Host::Onion(onion) => {
250                // The HS is identified by the last two domain name components
251                let rhs = onion
252                    .rmatch_indices('.')
253                    .nth(1)
254                    .map(|(i, _)| i + 1)
255                    .unwrap_or(0);
256                let rhs = &onion[rhs..];
257                let hsid = rhs.parse()?;
258                StreamInstructions::Hs {
259                    hsid,
260                    port,
261                    hostname: onion,
262                }
263            }
264        })
265    }
266
267    /// Get instructions for how to make a stream to this address
268    pub(crate) fn into_resolve_instructions(
269        self,
270        cfg: &crate::config::ClientAddrConfig,
271        prefs: &StreamPrefs,
272    ) -> Result<ResolveInstructions, ErrorDetail> {
273        // We defer enforcing the config until we see if this is a .onion,
274        // in which case it's always doomed and we want to return *our* error,
275        // not any problem with the configuration or preferences.
276        // But we must *calculate* the error now because instructions consumes self.
277        let enforce_config_result = self.enforce_config(cfg, prefs);
278
279        // This IEFE is so that any use of `return` doesn't bypass
280        // checking the enforce_config result
281        let instructions = (move || {
282            Ok(match self.host {
283                Host::Hostname(hostname) => ResolveInstructions::Exit(hostname),
284                Host::Ip(ip) => ResolveInstructions::Return(vec![ip]),
285                Host::Onion(_) => return Err(ErrorDetail::OnionAddressResolveRequest),
286            })
287        })()?;
288
289        let () = enforce_config_result?;
290
291        Ok(instructions)
292    }
293
294    /// Return true if the `host` in this address is local.
295    fn is_local(&self) -> bool {
296        self.host.is_local()
297    }
298
299    /// Give an error if this address doesn't conform to the rules set in
300    /// `cfg`.
301    fn enforce_config(
302        &self,
303        cfg: &crate::config::ClientAddrConfig,
304        #[allow(unused_variables)] // will only be used in certain configurations
305        prefs: &StreamPrefs,
306    ) -> Result<(), ErrorDetail> {
307        if !cfg.allow_local_addrs && self.is_local() {
308            return Err(ErrorDetail::LocalAddress);
309        }
310
311        if let Host::Hostname(addr) = &self.host {
312            if !is_valid_hostname(addr) {
313                // This ought not to occur, because it violates Host's invariant
314                return Err(ErrorDetail::InvalidHostname);
315            }
316            if addr.ends_with_ignore_ascii_case(HSID_ONION_SUFFIX) {
317                // This ought not to occur, because it violates Host's invariant
318                return Err(ErrorDetail::OnionAddressNotSupported);
319            }
320        }
321
322        if let Host::Onion(_name) = &self.host {
323            cfg_if::cfg_if! {
324                if #[cfg(feature = "onion-service-client")] {
325                    if !prefs.connect_to_onion_services.as_bool().unwrap_or(cfg.allow_onion_addrs) {
326                        return Err(ErrorDetail::OnionAddressDisabled);
327                    }
328                } else {
329                    return Err(ErrorDetail::OnionAddressNotSupported);
330                }
331            }
332        }
333
334        Ok(())
335    }
336}
337
338impl std::fmt::Display for TorAddr {
339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340        match self.host {
341            Host::Ip(IpAddr::V6(addr)) => write!(f, "[{}]:{}", addr, self.port),
342            _ => write!(f, "{}:{}", self.host, self.port),
343        }
344    }
345}
346
347/// An error created while making or using a [`TorAddr`].
348//
349// NOTE: Unlike ErrorDetail, this is a `pub` enum: Do not make breaking changes
350// to it, or expose lower-level errors in it, without careful consideration!
351#[derive(Debug, Error, Clone, Eq, PartialEq)]
352#[non_exhaustive]
353pub enum TorAddrError {
354    /// Tried to parse a string that can never be interpreted as a valid host.
355    #[error("String can never be a valid hostname")]
356    InvalidHostname,
357    /// Tried to parse a string as an `address:port`, but it had no port.
358    #[error("No port found in string")]
359    NoPort,
360    /// Tried to parse a port that wasn't a valid nonzero `u16`.
361    #[error("Could not parse port")]
362    BadPort,
363}
364
365impl HasKind for TorAddrError {
366    fn kind(&self) -> ErrorKind {
367        use ErrorKind as EK;
368        use TorAddrError as TAE;
369
370        match self {
371            TAE::InvalidHostname => EK::InvalidStreamTarget,
372            TAE::NoPort => EK::InvalidStreamTarget,
373            TAE::BadPort => EK::InvalidStreamTarget,
374        }
375    }
376}
377
378/// A host that Tor can connect to: either a hostname or an IP address.
379//
380// We use `String` in here, and pass that directly to (for example)
381// `HsId::from_str`, or `begin_stream`.
382// In theory we could use a couple of newtypes or something, but
383//  * The stringly-typed `HsId::from_str` call (on a string known to end `.onion`)
384//    appears precisely in `into_stream_instructions` which knows what it's doing;
385//  * The stringly-typed .onion domain name must be passed in the
386//    StreamInstructions so that we can send it to the HS for its vhosting.
387#[derive(Clone, Debug, Eq, PartialEq)]
388enum Host {
389    /// A hostname.
390    ///
391    /// This variant should never be used if the `Ip`
392    /// variant could be used instead.
393    /// Ie, it must not be a stringified IP address.
394    ///
395    /// Likewise, this variant must *not* be used for a `.onion` address.
396    /// Even if we have `.onion` support compiled out, we use the `Onion` variant for that.
397    ///
398    /// But, this variant might *not* be on the public internet.
399    /// For example, it might be `localhost`.
400    Hostname(String),
401    /// An IP address.
402    Ip(IpAddr),
403    /// The address of a hidden service (`.onion` service).
404    ///
405    /// We haven't validated that the base32 makes any kind of sense, yet.
406    /// We do that when we try to connect.
407    Onion(String),
408}
409
410impl FromStr for Host {
411    type Err = TorAddrError;
412    fn from_str(s: &str) -> Result<Host, TorAddrError> {
413        if s.ends_with_ignore_ascii_case(".onion") && is_valid_hostname(s) {
414            Ok(Host::Onion(s.to_owned()))
415        } else if let Ok(ip_addr) = s.parse() {
416            Ok(Host::Ip(ip_addr))
417        } else if is_valid_hostname(s) {
418            // TODO(nickm): we might someday want to reject some kinds of bad
419            // hostnames here, rather than when we're about to connect to them.
420            // But that would be an API break, and maybe not what people want.
421            // Maybe instead we should have a method to check whether a hostname
422            // is "bad"? Not sure; we'll need to decide the right behavior here.
423            Ok(Host::Hostname(s.to_owned()))
424        } else {
425            Err(TorAddrError::InvalidHostname)
426        }
427    }
428}
429
430impl Host {
431    /// Return true if this address is one that is "internal": that is,
432    /// relative to the particular host that is resolving it.
433    fn is_local(&self) -> bool {
434        match self {
435            Host::Hostname(name) => name.eq_ignore_ascii_case("localhost"),
436            // TODO: use is_global once it's stable, perhaps.
437            // NOTE: Contrast this with is_sufficiently_private in tor-hsproxy,
438            // which has a different purpose. Also see #1159.
439            // The purpose of _this_ test is to find addresses that cannot
440            // meaningfully be connected to over Tor, and that the exit
441            // will not accept.
442            Host::Ip(IpAddr::V4(ip)) => ip.is_loopback() || ip.is_private(),
443            Host::Ip(IpAddr::V6(ip)) => ip.is_loopback(),
444            Host::Onion(_) => false,
445        }
446    }
447}
448
449impl std::fmt::Display for Host {
450    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451        match self {
452            Host::Hostname(s) => Display::fmt(s, f),
453            Host::Ip(ip) => Display::fmt(ip, f),
454            Host::Onion(onion) => Display::fmt(onion, f),
455        }
456    }
457}
458
459impl IntoTorAddr for TorAddr {
460    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
461        Ok(self)
462    }
463}
464
465impl<A: IntoTorAddr + Clone> IntoTorAddr for &A {
466    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
467        self.clone().into_tor_addr()
468    }
469}
470
471impl IntoTorAddr for &str {
472    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
473        if let Ok(sa) = SocketAddr::from_str(self) {
474            TorAddr::new(Host::Ip(sa.ip()), sa.port())
475        } else {
476            let (host, port) = self.rsplit_once(':').ok_or(TorAddrError::NoPort)?;
477            let host = host.parse()?;
478            let port = port.parse().map_err(|_| TorAddrError::BadPort)?;
479            TorAddr::new(host, port)
480        }
481    }
482}
483
484impl IntoTorAddr for String {
485    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
486        self[..].into_tor_addr()
487    }
488}
489
490impl FromStr for TorAddr {
491    type Err = TorAddrError;
492    fn from_str(s: &str) -> Result<Self, TorAddrError> {
493        s.into_tor_addr()
494    }
495}
496
497impl IntoTorAddr for (&str, u16) {
498    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
499        let (host, port) = self;
500        let host = host.parse()?;
501        TorAddr::new(host, port)
502    }
503}
504
505impl IntoTorAddr for (String, u16) {
506    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
507        let (host, port) = self;
508        (&host[..], port).into_tor_addr()
509    }
510}
511
512impl<T: DangerouslyIntoTorAddr + Clone> DangerouslyIntoTorAddr for &T {
513    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
514        self.clone().into_tor_addr_dangerously()
515    }
516}
517
518impl DangerouslyIntoTorAddr for (IpAddr, u16) {
519    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
520        let (addr, port) = self;
521        TorAddr::new(Host::Ip(addr), port)
522    }
523}
524
525impl DangerouslyIntoTorAddr for (Ipv4Addr, u16) {
526    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
527        let (addr, port) = self;
528        TorAddr::new(Host::Ip(addr.into()), port)
529    }
530}
531
532impl DangerouslyIntoTorAddr for (Ipv6Addr, u16) {
533    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
534        let (addr, port) = self;
535        TorAddr::new(Host::Ip(addr.into()), port)
536    }
537}
538
539impl DangerouslyIntoTorAddr for SocketAddr {
540    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
541        let (addr, port) = (self.ip(), self.port());
542        (addr, port).into_tor_addr_dangerously()
543    }
544}
545
546impl DangerouslyIntoTorAddr for SocketAddrV4 {
547    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
548        let (addr, port) = (self.ip(), self.port());
549        (*addr, port).into_tor_addr_dangerously()
550    }
551}
552
553impl DangerouslyIntoTorAddr for SocketAddrV6 {
554    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
555        let (addr, port) = (self.ip(), self.port());
556        (*addr, port).into_tor_addr_dangerously()
557    }
558}
559
560/// Check whether `hostname` is a valid hostname or not.
561///
562/// (Note that IPv6 addresses don't follow these rules.)
563fn is_valid_hostname(hostname: &str) -> bool {
564    hostname_validator::is_valid(hostname)
565}
566
567#[cfg(test)]
568mod test {
569    // @@ begin test lint list maintained by maint/add_warning @@
570    #![allow(clippy::bool_assert_comparison)]
571    #![allow(clippy::clone_on_copy)]
572    #![allow(clippy::dbg_macro)]
573    #![allow(clippy::mixed_attributes_style)]
574    #![allow(clippy::print_stderr)]
575    #![allow(clippy::print_stdout)]
576    #![allow(clippy::single_char_pattern)]
577    #![allow(clippy::unwrap_used)]
578    #![allow(clippy::unchecked_duration_subtraction)]
579    #![allow(clippy::useless_vec)]
580    #![allow(clippy::needless_pass_by_value)]
581    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
582    use super::*;
583
584    #[test]
585    fn test_error_kind() {
586        use tor_error::ErrorKind as EK;
587
588        assert_eq!(
589            TorAddrError::InvalidHostname.kind(),
590            EK::InvalidStreamTarget
591        );
592        assert_eq!(TorAddrError::NoPort.kind(), EK::InvalidStreamTarget);
593        assert_eq!(TorAddrError::BadPort.kind(), EK::InvalidStreamTarget);
594    }
595
596    /// Make a `StreamPrefs` with `.onion` enabled, if cfg-enabled
597    fn mk_stream_prefs() -> StreamPrefs {
598        let prefs = crate::StreamPrefs::default();
599
600        #[cfg(feature = "onion-service-client")]
601        let prefs = {
602            let mut prefs = prefs;
603            prefs.connect_to_onion_services(tor_config::BoolOrAuto::Explicit(true));
604            prefs
605        };
606
607        prefs
608    }
609
610    #[test]
611    fn validate_hostname() {
612        // Valid hostname tests
613        assert!(is_valid_hostname("torproject.org"));
614        assert!(is_valid_hostname("Tor-Project.org"));
615        assert!(is_valid_hostname("example.onion"));
616        assert!(is_valid_hostname("some.example.onion"));
617
618        // Invalid hostname tests
619        assert!(!is_valid_hostname("-torproject.org"));
620        assert!(!is_valid_hostname("_torproject.org"));
621        assert!(!is_valid_hostname("tor_project1.org"));
622        assert!(!is_valid_hostname("iwanna$money.org"));
623    }
624
625    #[test]
626    fn validate_addr() {
627        use crate::err::ErrorDetail;
628        fn val<A: IntoTorAddr>(addr: A) -> Result<TorAddr, ErrorDetail> {
629            let toraddr = addr.into_tor_addr()?;
630            toraddr.enforce_config(&Default::default(), &mk_stream_prefs())?;
631            Ok(toraddr)
632        }
633
634        assert!(val("[2001:db8::42]:20").is_ok());
635        assert!(val(("2001:db8::42", 20)).is_ok());
636        assert!(val(("198.151.100.42", 443)).is_ok());
637        assert!(val("198.151.100.42:443").is_ok());
638        assert!(val("www.torproject.org:443").is_ok());
639        assert!(val(("www.torproject.org", 443)).is_ok());
640
641        // When HS disabled, tested elsewhere, see: stream_instructions, prefs_onion_services
642        #[cfg(feature = "onion-service-client")]
643        {
644            assert!(val("example.onion:80").is_ok());
645            assert!(val(("example.onion", 80)).is_ok());
646
647            match val("eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443") {
648                Ok(TorAddr {
649                    host: Host::Onion(_),
650                    ..
651                }) => {}
652                x => panic!("{x:?}"),
653            }
654        }
655
656        assert!(matches!(
657            val("-foobar.net:443"),
658            Err(ErrorDetail::InvalidHostname)
659        ));
660        assert!(matches!(
661            val("www.torproject.org"),
662            Err(ErrorDetail::Address(TorAddrError::NoPort))
663        ));
664
665        assert!(matches!(
666            val("192.168.0.1:80"),
667            Err(ErrorDetail::LocalAddress)
668        ));
669        assert!(matches!(
670            val(TorAddr::new(Host::Hostname("foo@bar".to_owned()), 553).unwrap()),
671            Err(ErrorDetail::InvalidHostname)
672        ));
673        assert!(matches!(
674            val(TorAddr::new(Host::Hostname("foo.onion".to_owned()), 80).unwrap()),
675            Err(ErrorDetail::OnionAddressNotSupported)
676        ));
677    }
678
679    #[test]
680    fn local_addrs() {
681        fn is_local_hostname(s: &str) -> bool {
682            let h: Host = s.parse().unwrap();
683            h.is_local()
684        }
685
686        assert!(is_local_hostname("localhost"));
687        assert!(is_local_hostname("loCALHOST"));
688        assert!(is_local_hostname("127.0.0.1"));
689        assert!(is_local_hostname("::1"));
690        assert!(is_local_hostname("192.168.0.1"));
691
692        assert!(!is_local_hostname("www.example.com"));
693    }
694
695    #[test]
696    fn is_ip_address() {
697        fn ip(s: &str) -> bool {
698            TorAddr::from(s).unwrap().is_ip_address()
699        }
700
701        assert!(ip("192.168.0.1:80"));
702        assert!(ip("[::1]:80"));
703        assert!(ip("[2001:db8::42]:65535"));
704        assert!(!ip("example.com:80"));
705        assert!(!ip("example.onion:80"));
706    }
707
708    #[test]
709    fn stream_instructions() {
710        use StreamInstructions as SI;
711
712        fn sap(s: &str) -> Result<StreamInstructions, ErrorDetail> {
713            TorAddr::from(s)
714                .unwrap()
715                .into_stream_instructions(&Default::default(), &mk_stream_prefs())
716        }
717
718        assert_eq!(
719            sap("[2001:db8::42]:9001").unwrap(),
720            SI::Exit {
721                hostname: "2001:db8::42".to_owned(),
722                port: 9001
723            },
724        );
725        assert_eq!(
726            sap("example.com:80").unwrap(),
727            SI::Exit {
728                hostname: "example.com".to_owned(),
729                port: 80
730            },
731        );
732
733        {
734            let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
735            let onion = format!("sss1234.www.{}.onion", b32);
736            let got = sap(&format!("{}:443", onion));
737
738            #[cfg(feature = "onion-service-client")]
739            assert_eq!(
740                got.unwrap(),
741                SI::Hs {
742                    hsid: format!("{}.onion", b32).parse().unwrap(),
743                    hostname: onion,
744                    port: 443,
745                }
746            );
747
748            #[cfg(not(feature = "onion-service-client"))]
749            assert!(matches!(got, Err(ErrorDetail::OnionAddressNotSupported)));
750        }
751    }
752
753    #[test]
754    fn resolve_instructions() {
755        use ResolveInstructions as RI;
756
757        fn sap(s: &str) -> Result<ResolveInstructions, ErrorDetail> {
758            TorAddr::from(s)
759                .unwrap()
760                .into_resolve_instructions(&Default::default(), &Default::default())
761        }
762
763        assert_eq!(
764            sap("[2001:db8::42]:9001").unwrap(),
765            RI::Return(vec!["2001:db8::42".parse().unwrap()]),
766        );
767        assert_eq!(
768            sap("example.com:80").unwrap(),
769            RI::Exit("example.com".to_owned()),
770        );
771        assert!(matches!(
772            sap("example.onion:80"),
773            Err(ErrorDetail::OnionAddressResolveRequest),
774        ));
775    }
776
777    #[test]
778    fn bad_ports() {
779        assert_eq!(
780            TorAddr::from("www.example.com:squirrel"),
781            Err(TorAddrError::BadPort)
782        );
783        assert_eq!(
784            TorAddr::from("www.example.com:0"),
785            Err(TorAddrError::BadPort)
786        );
787    }
788
789    #[test]
790    fn prefs_onion_services() {
791        use crate::err::ErrorDetailDiscriminants;
792        use tor_error::{ErrorKind, HasKind as _};
793        use ErrorDetailDiscriminants as EDD;
794        use ErrorKind as EK;
795
796        #[allow(clippy::redundant_closure)] // for symmetry with prefs_of, below, and clarity
797        let prefs_def = || StreamPrefs::default();
798
799        let addr: TorAddr = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443"
800            .parse()
801            .unwrap();
802
803        fn map(
804            got: Result<impl Sized, ErrorDetail>,
805        ) -> Result<(), (ErrorDetailDiscriminants, ErrorKind)> {
806            got.map(|_| ())
807                .map_err(|e| (ErrorDetailDiscriminants::from(&e), e.kind()))
808        }
809
810        let check_stream = |prefs, expected| {
811            let got = addr
812                .clone()
813                .into_stream_instructions(&Default::default(), &prefs);
814            assert_eq!(map(got), expected, "{prefs:?}");
815        };
816        let check_resolve = |prefs| {
817            let got = addr
818                .clone()
819                .into_resolve_instructions(&Default::default(), &prefs);
820            // This should be OnionAddressResolveRequest no matter if .onion is compiled in or enabled.
821            // Since compiling it in, or enabling it, won't help.
822            let expected = Err((EDD::OnionAddressResolveRequest, EK::NotImplemented));
823            assert_eq!(map(got), expected, "{prefs:?}");
824        };
825
826        cfg_if::cfg_if! {
827            if #[cfg(feature = "onion-service-client")] {
828                use tor_config::BoolOrAuto as B;
829                let prefs_of = |yn| {
830                    let mut prefs = StreamPrefs::default();
831                    prefs.connect_to_onion_services(yn);
832                    prefs
833                };
834                check_stream(prefs_def(), Ok(()));
835                check_stream(prefs_of(B::Auto), Ok(()));
836                check_stream(prefs_of(B::Explicit(true)), Ok(()));
837                check_stream(prefs_of(B::Explicit(false)), Err((EDD::OnionAddressDisabled, EK::ForbiddenStreamTarget)));
838
839                check_resolve(prefs_def());
840                check_resolve(prefs_of(B::Auto));
841                check_resolve(prefs_of(B::Explicit(true)));
842                check_resolve(prefs_of(B::Explicit(false)));
843            } else {
844                check_stream(prefs_def(), Err((EDD::OnionAddressNotSupported, EK::FeatureDisabled)));
845
846                check_resolve(prefs_def());
847            }
848        }
849    }
850
851    #[test]
852    fn convert_safe() {
853        fn check<A: IntoTorAddr>(a: A, s: &str) {
854            let a1 = TorAddr::from(a).unwrap();
855            let a2 = s.parse().unwrap();
856            assert_eq!(a1, a2);
857            assert_eq!(&a1.to_string(), s);
858        }
859
860        check(("www.example.com", 8000), "www.example.com:8000");
861        check(
862            TorAddr::from(("www.example.com", 8000)).unwrap(),
863            "www.example.com:8000",
864        );
865        check(
866            TorAddr::from(("www.example.com", 8000)).unwrap(),
867            "www.example.com:8000",
868        );
869        let addr = "[2001:db8::0042]:9001".to_owned();
870        check(&addr, "[2001:db8::42]:9001");
871        check(addr, "[2001:db8::42]:9001");
872        check(("2001:db8::0042".to_owned(), 9001), "[2001:db8::42]:9001");
873        check(("example.onion", 80), "example.onion:80");
874    }
875
876    #[test]
877    fn convert_dangerous() {
878        fn check<A: DangerouslyIntoTorAddr>(a: A, s: &str) {
879            let a1 = TorAddr::dangerously_from(a).unwrap();
880            let a2 = TorAddr::from(s).unwrap();
881            assert_eq!(a1, a2);
882            assert_eq!(&a1.to_string(), s);
883        }
884
885        let ip: IpAddr = "203.0.133.6".parse().unwrap();
886        let ip4: Ipv4Addr = "203.0.133.7".parse().unwrap();
887        let ip6: Ipv6Addr = "2001:db8::42".parse().unwrap();
888        let sa: SocketAddr = "203.0.133.8:80".parse().unwrap();
889        let sa4: SocketAddrV4 = "203.0.133.8:81".parse().unwrap();
890        let sa6: SocketAddrV6 = "[2001:db8::43]:82".parse().unwrap();
891
892        // This tests impl DangerouslyIntoTorAddr for &T
893        #[allow(clippy::needless_borrow)]
894        #[allow(clippy::needless_borrows_for_generic_args)]
895        check(&(ip, 443), "203.0.133.6:443");
896        check((ip, 443), "203.0.133.6:443");
897        check((ip4, 444), "203.0.133.7:444");
898        check((ip6, 445), "[2001:db8::42]:445");
899        check(sa, "203.0.133.8:80");
900        check(sa4, "203.0.133.8:81");
901        check(sa6, "[2001:db8::43]:82");
902    }
903}