summaryrefslogtreecommitdiff
path: root/crates/tor-proto/src/client/rpc.rs
blob: 2c04a3023dfd9443fb68a7acffbed6a38d96cc93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//! RPC support for client tor-proto objects.

use std::{collections::HashMap, net::SocketAddr, sync::Arc};

use derive_deftly::Deftly;
use tor_linkspec::{HasAddrs, HasRelayIds};
use tor_llcrypto::pk;
use tor_rpcbase::{self as rpc, SingleIdResponse};

use crate::{
    ClientTunnel,
    client::stream::{ClientDataStreamCtrl, ClientStreamCtrl},
};

/// RPC method that returns the tunnel for a given object.
///
/// This is currently implemented for Data streams,
/// but could in the future be implemented for other types.
///
/// # In the Arti RPC System
///
/// Return a tunnel associated with a given object.
///
/// (A tunnel is a collection of one or more circuits
/// used to transmit data.)
///
/// Gives an error if the object is not associated with a tunnel,
/// or if the tunnel was closed.
///
/// The returned ObjectId is a handle to a `ClientTunnel`.
/// The caller should drop this ObjectId when they are done with the ClientTunnel.
#[derive(Debug, serde::Deserialize, Deftly)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:get_tunnel"))]
#[non_exhaustive]
pub struct GetTunnel {}

impl rpc::RpcMethod for GetTunnel {
    type Output = rpc::SingleIdResponse;
    type Update = rpc::NoUpdates;
}

/// RPC method to describe the path for an object.
///
/// This is currently implemented for [`ClientTunnel`],
/// but could in the future be implemented for other types.
///
/// # In the Arti RPC System
///
/// Describe the path(s) of a tunnel through the Tor network.
///
/// (Because of [Conflux], a tunnel can have multiple paths.
/// This method describes the members of each one.)
///
/// If `include_deprecated_ids` is true,
/// the output includes deprecated node identity types,
/// including the RSA identity.
/// Otherwise, only non-deprecated identities are included.
///
/// [Conflux]: https://spec.torproject.org/proposals/329-traffic-splitting.html
#[derive(Debug, serde::Deserialize, Deftly)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:describe_path"))]
#[non_exhaustive]
pub struct DescribePath {
    /// If true, the output will include deprecated node identity types.
    #[serde(default)]
    include_deprecated_ids: bool,
}

impl rpc::RpcMethod for DescribePath {
    type Output = PathDescription;
    type Update = rpc::NoUpdates;
}

/// A RPC-level description for a single entry in a tunnel or circuit path.
#[derive(serde::Serialize, Clone, Debug)]
#[non_exhaustive]
#[serde(rename_all = "snake_case")]
pub enum PathEntry {
    /// A hop not corresponding to a known relay.
    ///
    /// Typically, this is the join point for a rendezvous circuit for
    /// communicating with or as an onion service.
    VirtualHop {},

    /// Return
    KnownRelay {
        /// A set of IDs for this Tor relay.
        ///
        /// Each ID represents a long-term public key used to identify the relay.
        /// Deprecated ID types are not included, unless they were specifically requested.
        ids: RelayIds,

        /// A list of the relay's addresses.
        addrs: Vec<SocketAddr>,
    },
}

/// Serializable container of relay identities.
///
/// Differs from [`tor_linkspec::RelayIds`] in is serialize behavior;
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/2477>.
///
/// Every relay will have at least one ID.  On the current Tor network
/// as of April 2026, every relay has an Ed25519 ID.
/// (This may not always be the case in the future;
/// for example, if we migrate to ML-DSA keys,
/// we may eventually retire Ed25519 IDs.)
#[derive(Clone, Debug, serde::Serialize)]
pub struct RelayIds {
    /// Copy of the ed25519 id from the underlying ChanTarget.
    #[serde(rename = "ed25519", skip_serializing_if = "Option::is_none")]
    ed_identity: Option<pk::ed25519::Ed25519Identity>,

    /// Copy of the rsa id from the underlying ChanTarget.
    ///
    /// This is a deprecated ID type.
    #[serde(rename = "rsa", skip_serializing_if = "Option::is_none")]
    rsa_identity: Option<pk::rsa::RsaIdentity>,
}

/// A description of a tunnel's path.
///
/// Note that tunnels are potentially made of multiple circuits, even though
/// Arti (as of April 2026) does not yet build Conflux tunnels.
/// Therefore, users should make sure to handle multi-path tunnels here.
#[derive(serde::Serialize, Clone, Debug)]
pub struct PathDescription {
    /// The entries in a given tunnel's path(s).
    ///
    /// Within each path, entries are ordered from first (closest to the client)
    /// to last (farthest from the client).
    ///
    /// Since tunnels can have multiple paths, each path is identified with a string.
    /// The actual content of these strings is not documented,
    /// but the string identifying each tunnel is stable.
    ///
    /// (That is, if you query a tunnel's path description twice,
    /// each path will have the same identifying string both times.
    /// If you get a new identifying string,
    /// it represents a path that was previously not part of the tunnel.)
    path: HashMap<String, Vec<PathEntry>>,
}

impl PathEntry {
    /// Construct a PathEntry from a PathEntry returned by a circuit.
    fn from_client_entry(
        detail: &crate::client::circuit::PathEntry,
        command: &DescribePath,
    ) -> Self {
        let Some(owned_chan_target) = detail.as_chan_target() else {
            return PathEntry::VirtualHop {};
        };

        let ids = tor_linkspec::RelayIds::from_relay_ids(owned_chan_target);
        let ids = RelayIds {
            ed_identity: ids.ed_identity().cloned(),
            rsa_identity: if command.include_deprecated_ids {
                ids.rsa_identity().cloned()
            } else {
                None
            },
        };
        let addrs = owned_chan_target.addrs().collect();
        PathEntry::KnownRelay { ids, addrs }
    }
}

/// Helper: Return the [`ClientTunnel`] for a [`ClientDataStreamCtrl`],
/// or an RPC error if the stream isn't attached to a tunnel.
fn client_stream_tunnel(stream: &ClientDataStreamCtrl) -> Result<Arc<ClientTunnel>, rpc::RpcError> {
    stream.tunnel().ok_or_else(|| {
        rpc::RpcError::new(
            "Stream was not attached to a tunnel".to_string(),
            rpc::RpcErrorKind::RequestError,
        )
    })
}

/// Helper: Return a [`PathDescription`] for a [`ClientTunnel`]
fn tunnel_path(tunnel: &ClientTunnel, method: &DescribePath) -> PathDescription {
    let path = tunnel
        .tagged_paths()
        .into_iter()
        .map(|(id, path)| {
            let id = id.display_chan_circ().to_string();
            let path = path
                .iter()
                .map(|hop| PathEntry::from_client_entry(hop, method))
                .collect();
            (id, path)
        })
        .collect();
    PathDescription { path }
}

/// Implementation function: implements GetTunnel on ClientDataStreamCtrl.
async fn client_data_stream_ctrl_get_tunnel(
    target: Arc<ClientDataStreamCtrl>,
    _method: Box<GetTunnel>,
    ctx: Arc<dyn rpc::Context>,
) -> Result<rpc::SingleIdResponse, rpc::RpcError> {
    let tunnel: Arc<dyn rpc::Object> = client_stream_tunnel(&target)? as _;
    let id = ctx.register_owned(tunnel);
    Ok(SingleIdResponse::from(id))
}

/// Implementation function: implements DescribePath on a ClientTunnel.
async fn client_tunnel_describe_path(
    target: Arc<ClientTunnel>,
    method: Box<DescribePath>,
    _ctx: Arc<dyn rpc::Context>,
) -> Result<PathDescription, rpc::RpcError> {
    Ok(tunnel_path(&target, &method))
}

/// Implementation function: implements DescribePath on a ClientDataStreamCtrl.
async fn client_data_stream_ctrl_describe_path(
    target: Arc<ClientDataStreamCtrl>,
    method: Box<DescribePath>,
    _ctx: Arc<dyn rpc::Context>,
) -> Result<PathDescription, rpc::RpcError> {
    let tunnel = client_stream_tunnel(&target)?;
    Ok(tunnel_path(&tunnel, &method))
}

rpc::static_rpc_invoke_fn! {
    client_data_stream_ctrl_get_tunnel;
    client_tunnel_describe_path;
    client_data_stream_ctrl_describe_path;
}