tor_hsclient/relay_info.rs
1//! Translate relay information from the formats used in the onion service
2//! protocol into `CircTarget`s that we can use for building circuits.
3//!
4//! (Later this will include support for INTRODUCE2 messages too.)
5
6use tor_error::{into_internal, HasRetryTime, RetryTime};
7use tor_linkspec::{
8 decode::Strictness, verbatim::VerbatimLinkSpecCircTarget, CircTarget, EncodedLinkSpec,
9 OwnedChanTargetBuilder, OwnedCircTarget,
10};
11use tor_llcrypto::pk::curve25519;
12use tor_netdir::NetDir;
13use tor_netdoc::doc::hsdesc::IntroPointDesc;
14
15/// Helper: create a [`CircTarget`] from its component parts as provided by
16/// another party on the network.
17///
18/// This function is used to build a `CircTarget` from an `IntroPointDesc` (for
19/// extending to an introduction point). Later, it can also be used to build a
20/// CircTarget from an `Introduce2` message (for extending to a rendezvous
21/// point).
22//
23// TODO (#1223): This function is very similar to a block of code in
24// `tor-hsservice`. Can/should we unify them?
25fn circtarget_from_pieces(
26 linkspecs: &[EncodedLinkSpec],
27 ntor_onion_key: &curve25519::PublicKey,
28 netdir: &NetDir,
29) -> Result<impl CircTarget, InvalidTarget> {
30 let mut bld = OwnedCircTarget::builder();
31 // Decode the link specifiers and use them to find out what we can about
32 // this relay.
33 *bld.chan_target() =
34 OwnedChanTargetBuilder::from_encoded_linkspecs(Strictness::Standard, linkspecs)?;
35 // Look up the relay in the directory, to see:
36 // 1) if it is flatly impossible,
37 // 2) what subprotocols we should assume it implements.
38 let protocols = {
39 let chan_target = bld.chan_target().build().map_err(into_internal!(
40 "from_linkspecs gave us a non-working ChanTargetBuilder"
41 ))?;
42 match netdir.by_ids_detailed(&chan_target)? {
43 Some(relay) => relay.protovers().clone(),
44 None => netdir.relay_protocol_status().required_protocols().clone(),
45 }
46 };
47 bld.protocols(protocols);
48 bld.ntor_onion_key(*ntor_onion_key);
49 let circ_target = bld.build().map_err(into_internal!(
50 "somehow we made an invalid CircTargetBuilder"
51 ))?;
52 Ok(VerbatimLinkSpecCircTarget::new(
53 circ_target,
54 linkspecs.to_vec(),
55 ))
56}
57
58/// Construct a [`CircTarget`] from a provided [`IntroPointDesc`].
59///
60/// Onion service clients use this function to convert an `IntroPointDesc` in
61/// the onion service descriptor into a form that they can use when building a
62/// circuit to an introduction point.
63///
64/// The `netdir` argument is used to fill in missing information about the
65/// target relay, and to make sure that the target relay's identities are not
66/// inconsistent with the rest of the network.
67pub(crate) fn ipt_to_circtarget(
68 desc: &IntroPointDesc,
69 netdir: &NetDir,
70) -> Result<impl CircTarget, InvalidTarget> {
71 circtarget_from_pieces(desc.link_specifiers(), desc.ipt_ntor_key(), netdir)
72}
73
74/// We were given unusable information about an introduction point or rendezvous
75/// point.
76//
77// This is returned by `ipt_to_circtarget`. It will also be used for rendezvous
78// points when we implement the HS server side.
79// At that point, this module will need to move to a crate where it can be used
80// by the HS server code.
81#[derive(Clone, Debug, thiserror::Error)]
82#[non_exhaustive]
83pub enum InvalidTarget {
84 /// The provided link specifiers included some that, when we tried to parse
85 /// them, proved to be malformed.
86 #[error("Malformed channel target information provided")]
87 UnparseableChanTargetInfo(#[from] tor_bytes::Error),
88
89 /// The provided link specifiers were inconsistent with one another, or missing
90 /// key information.
91 #[error("Invalid channel target information provided")]
92 InvalidChanTargetInfo(#[from] tor_linkspec::decode::ChanTargetDecodeError),
93
94 /// The provided relay identities (in the link specifiers) described a relay
95 /// which, according to the network directory, cannot possibly exist.
96 #[error("Impossible combination of relay identities")]
97 ImpossibleRelayIds(#[from] tor_netdir::RelayLookupError),
98
99 /// An internal error occurred.
100 #[error("{0}")]
101 Bug(#[from] tor_error::Bug),
102}
103
104/// When to maybe retry *with the same inputs* that generated this error.
105///
106/// When returned from `ipt_to_circtarget`, that means this is when to retry
107/// the *same introduction point* for the *same hidden service*.
108///
109/// "The same introduction point" means one with precisely the same set of identities
110/// and link specifiers.
111//
112// Note about correctness, and introduction point identity:
113//
114// We use this as part of HasRetryTime for FailedAttemptError.
115// HasRetryTime for FailedAttemptError is used for selecting which intro point to retry.
116// Our introduction point experiences are recorded according to *one* relay identity,
117// not the complete set.
118//
119// Nevertheless, this is correct, because: we only select from, and record experiences for,
120// *usable* introduction points. An InvalidTarget error is detected early enough
121// to avoid regarding the introduction point as usable at all. So we never use
122// this RetryTime impl, here, to choose between introduction points.
123impl HasRetryTime for InvalidTarget {
124 fn retry_time(&self) -> RetryTime {
125 use InvalidTarget as IT;
126 use RetryTime as RT;
127 match self {
128 IT::UnparseableChanTargetInfo(..) => RT::Never,
129 IT::InvalidChanTargetInfo(..) => RT::Never,
130 IT::ImpossibleRelayIds(..) => RT::Never,
131 IT::Bug(..) => RT::Never,
132 }
133 }
134}