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

            
6
use tor_error::{into_internal, HasRetryTime, RetryTime};
7
use tor_linkspec::{
8
    decode::Strictness, verbatim::VerbatimLinkSpecCircTarget, CircTarget, EncodedLinkSpec,
9
    OwnedChanTargetBuilder, OwnedCircTarget,
10
};
11
use tor_llcrypto::pk::curve25519;
12
use tor_netdir::NetDir;
13
use 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?
25
fn 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.
67
pub(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]
83
pub enum InvalidTarget {
84
    /// The provided link specifiers included some that, when we tried to parse
85
    /// them, proved to be misformed.
86
    #[error("Misformed 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.
123
impl 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
}