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 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
//! Declare RPC functionality on for the `arti-client` crate.
use async_trait::async_trait;
use derive_deftly::Deftly;
use dyn_clone::DynClone;
use futures::{SinkExt as _, StreamExt as _};
use serde::{Deserialize, Serialize};
use std::{net::IpAddr, sync::Arc};
use tor_proto::stream::DataStream;
use tor_rpcbase as rpc;
use tor_rtcompat::Runtime;
use crate::{StreamPrefs, TorAddr, TorClient};
impl<R: Runtime> TorClient<R> {
/// Ensure that every RPC method is registered for this instantiation of TorClient.
///
/// We can't use [`rpc::static_rpc_invoke_fn`] for these, since TorClient is
/// parameterized.
pub fn rpc_methods() -> Vec<rpc::dispatch::InvokerEnt> {
rpc::invoker_ent_list![
get_client_status::<R>,
watch_client_status::<R>,
isolated_client::<R>,
]
}
}
/// RPC method: Return the current ClientStatusInfo.
#[derive(Deftly, Debug, Serialize, Deserialize)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:get_client_status"))]
struct GetClientStatus {}
impl rpc::Method for GetClientStatus {
type Output = ClientStatusInfo;
type Update = rpc::NoUpdates;
type Error = rpc::RpcError;
}
/// RPC method: Run forever, delivering an updated view of the ClientStatusInfo whenever it changes.
///
/// (This method can return updates that have no visible changes.)
#[derive(Deftly, Debug, Serialize, Deserialize)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:watch_client_status"))]
struct WatchClientStatus {}
impl rpc::Method for WatchClientStatus {
type Output = rpc::Nil;
type Update = ClientStatusInfo;
type Error = rpc::RpcError;
}
/// RPC result: The reported status of a TorClient.
#[derive(Serialize, Deserialize)]
struct ClientStatusInfo {
/// True if the client is ready for traffic.
ready: bool,
/// Approximate estimate of how close the client is to being ready for traffic.
fraction: f32,
/// If present, a description of possible problem(s) that may be stopping
/// the client from using the Tor network.
blocked: Option<String>,
}
impl From<crate::status::BootstrapStatus> for ClientStatusInfo {
fn from(s: crate::status::BootstrapStatus) -> Self {
let ready = s.ready_for_traffic();
let fraction = s.as_frac();
let blocked = s.blocked().map(|b| b.to_string());
Self {
ready,
fraction,
blocked,
}
}
}
// NOTE: These functions could be defined as methods on TorClient<R>.
// I'm defining them like this to make it more clear that they are never
// invoked as client.method(), but only via the RPC system.
// We can revisit this later if we want.
// TODO RPC: Once we have one or two more get/watch combinations,
// we should look into some facility for automatically declaring them,
// so that their behavior stays uniform.
//
// See https://gitlab.torproject.org/tpo/core/arti/-/issues/1384#note_3023659
/// Invocable function to run [`GetClientStatus`] on a [`TorClient`].
async fn get_client_status<R: Runtime>(
client: Arc<TorClient<R>>,
_method: Box<GetClientStatus>,
_ctx: Box<dyn rpc::Context>,
) -> Result<ClientStatusInfo, rpc::RpcError> {
Ok(client.bootstrap_status().into())
}
/// Invocable function to run [`WatchClientStatus`] on a [`TorClient`].
async fn watch_client_status<R: Runtime>(
client: Arc<TorClient<R>>,
_method: Box<WatchClientStatus>,
_ctx: Box<dyn rpc::Context>,
mut updates: rpc::UpdateSink<ClientStatusInfo>,
) -> Result<rpc::Nil, rpc::RpcError> {
let mut events = client.bootstrap_events();
// Send the _current_ status, no matter what.
// (We do this after constructing er)
updates.send(client.bootstrap_status().into()).await?;
// Send additional updates whenever the status changes.
while let Some(status) = events.next().await {
updates.send(status.into()).await?;
}
// This can only happen if the client exits.
Ok(rpc::NIL)
}
/// RPC method: Return an owned ID for a new isolated client instance.
#[derive(Deftly, Debug, Serialize, Deserialize)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:isolated_client"))]
#[non_exhaustive]
pub struct IsolatedClient {}
impl rpc::Method for IsolatedClient {
type Output = rpc::SingletonId;
type Update = rpc::NoUpdates;
type Error = rpc::RpcError;
}
/// RPC method implementation: return a new isolated client based on a given client.
async fn isolated_client<R: Runtime>(
client: Arc<TorClient<R>>,
_method: Box<IsolatedClient>,
ctx: Box<dyn rpc::Context>,
) -> Result<rpc::SingletonId, rpc::RpcError> {
let new_client = Arc::new(client.isolated_client());
let client_id = ctx.register_owned(new_client);
Ok(rpc::SingletonId::from(client_id))
}
/// Type-erased error returned by ClientConnectionTarget.
//
// TODO RPC: It would be handy if this implemented HasErrorHint, but HasErrorHint is sealed.
// Perhaps we could go and solve our problem by implementing HasErrorHint on dyn StdError?
pub trait ClientConnectionError:
std::error::Error + tor_error::HasKind + DynClone + Send + Sync + seal::Sealed
{
}
impl<E> seal::Sealed for E where E: std::error::Error + tor_error::HasKind + DynClone + Send + Sync {}
impl<E> ClientConnectionError for E where
E: std::error::Error + tor_error::HasKind + DynClone + Send + Sync + seal::Sealed
{
}
impl std::error::Error for Box<dyn ClientConnectionError> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.as_ref().source()
}
}
dyn_clone::clone_trait_object!(ClientConnectionError);
/// module to seal the ClientConnectionError trait.
mod seal {
/// hidden trait to seal the ClientConnectionError trait.
#[allow(unreachable_pub)]
pub trait Sealed {}
}
/// Type alias for a Result return by ClientConnectionTarget
pub type ClientConnectionResult<T> = Result<T, Box<dyn ClientConnectionError>>;
/// An RPC-visible object that can be used as the target of SOCKS operations,
/// or other application-level connection attempts.
///
/// Only the RPC subsystem should use this type.
///
/// Semantically, you can consider this trait as a collection of three Methods
/// that certain RPC objects implement. We aren't implementing this directly
/// as rpc::Methods because they cannot (currently) return non-Serialize types.
//
// TODO RPC: Conceivably, we would like to apply this trait to types in lower-level crates: for
// example, we could put it onto ClientCirc, and let the application launch streams on a circuit
// directly. But if we did that, we wouldn't be able to downcast an ClientCirc from Arc<dyn Object>
// to this trait. Perhaps we need a clever solution.
//
// TODO RPC: This trait, along with ClientConnection{Error,Result}, have names that are just too
// long.
//
// TODO RPC: We might like to replace this with a special kind of RPC method;
// see #1403.
#[async_trait]
pub trait ClientConnectionTarget: Send + Sync {
/// As [`TorClient::connect_with_prefs`].
async fn connect_with_prefs(
&self,
target: &TorAddr,
prefs: &StreamPrefs,
) -> ClientConnectionResult<DataStream>;
/// As [`TorClient::resolve_with_prefs`].
async fn resolve_with_prefs(
&self,
hostname: &str,
prefs: &StreamPrefs,
) -> ClientConnectionResult<Vec<IpAddr>>;
/// As [`TorClient::resolve_ptr_with_prefs`].
async fn resolve_ptr_with_prefs(
&self,
addr: IpAddr,
prefs: &StreamPrefs,
) -> ClientConnectionResult<Vec<String>>;
}
#[async_trait]
impl<R: Runtime> ClientConnectionTarget for TorClient<R> {
async fn connect_with_prefs(
&self,
target: &TorAddr,
prefs: &StreamPrefs,
) -> ClientConnectionResult<DataStream> {
TorClient::connect_with_prefs(self, target, prefs)
.await
.map_err(|e| Box::new(e) as _)
}
async fn resolve_with_prefs(
&self,
hostname: &str,
prefs: &StreamPrefs,
) -> ClientConnectionResult<Vec<IpAddr>> {
TorClient::resolve_with_prefs(self, hostname, prefs)
.await
.map_err(|e| Box::new(e) as _)
}
async fn resolve_ptr_with_prefs(
&self,
addr: IpAddr,
prefs: &StreamPrefs,
) -> ClientConnectionResult<Vec<String>> {
TorClient::resolve_ptr_with_prefs(self, addr, prefs)
.await
.map_err(|e| Box::new(e) as _)
}
}