Commit 55813c83 authored by David Goulet's avatar David Goulet 🐼
Browse files

circmgr: New Tunnel object interface



Introduce the new Tunnel structs that is planned to expose publicly as a
replacement to `ClientCirc`.

Future commits will make those tunnel objects be used accross the code
base up until tor-proto which than handles Circuit directly.

Signed-off-by: David Goulet's avatarDavid Goulet <dgoulet@torproject.org>
parent 23144202
Loading
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -5751,6 +5751,7 @@ dependencies = [
 "async-trait",
 "bounded-vec-deque",
 "cfg-if",
 "derive-deftly 1.0.1",
 "derive_builder_fork_arti",
 "derive_more",
 "downcast-rs",
@@ -5772,6 +5773,7 @@ dependencies = [
 "thiserror 2.0.12",
 "tor-async-utils",
 "tor-basic-utils",
 "tor-cell",
 "tor-chanmgr",
 "tor-config",
 "tor-error",
+3 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ full = [
    "retry-error/full",
    "safelog/full",
    "tor-basic-utils/full",
    "tor-cell/full",
    "tor-chanmgr/full",
    "tor-config/full",
    "tor-error/full",
@@ -71,6 +72,7 @@ async-trait = "0.1.54"
bounded-vec-deque = "0.1"
cfg-if = "1.0.0"
derive_builder = { version = "0.11.2", package = "derive_builder_fork_arti" }
derive-deftly = { version = "~1.0.0", features = ["full", "beta"] }
derive_more = { version = "2.0.1", features = ["full"] }
downcast-rs = "2.0.1"
dyn-clone = "1.0.4"
@@ -89,6 +91,7 @@ static_assertions = "1"
thiserror = "2"
tor-async-utils = { version = "0.31.0", path = "../tor-async-utils" }
tor-basic-utils = { path = "../tor-basic-utils", version = "0.31.0" }
tor-cell = { path = "../tor-cell", version = "0.31.0" }
tor-chanmgr = { path = "../tor-chanmgr", version = "0.31.0" }
tor-config = { path = "../tor-config", version = "0.31.0" }
tor-error = { path = "../tor-error", version = "0.31.0", features = ["tracing"] }
+8 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ mod mgr;
mod mocks;
mod preemptive;
pub mod timeouts;
mod tunnel;
mod usage;

// Can't apply `visibility` to modules.
@@ -96,6 +97,13 @@ pub use err::Error;
pub use isolation::IsolationToken;
use tor_guardmgr::fallback::FallbackList;
pub use tor_guardmgr::{ClockSkewEvents, GuardMgrConfig, SkewEstimate};
pub use tunnel::{
    ClientDataTunnel, ClientDirTunnel, ClientMultiPathDataTunnel,
    ClientMultiPathOnionServiceDataTunnel, ClientOnionServiceDataTunnel,
    ClientOnionServiceDirTunnel, ClientOnionServiceIntroTunnel,
    ServiceMultiPathOnionServiceDataTunnel, ServiceOnionServiceDataTunnel,
    ServiceOnionServiceDirTunnel, ServiceOnionServiceIntroTunnel,
};
pub use usage::{TargetPort, TargetPorts};

pub use config::{
+433 −0
Original line number Diff line number Diff line
//! Code related to tunnel object that wraps the tor-proto tunnel.
//!
//! These tunnel types are part of the public API.

use derive_deftly::{define_derive_deftly, Deftly};
use std::{net::IpAddr, sync::Arc};

use tor_cell::relaycell::msg::AnyRelayMsg;
use tor_error::internal;
use tor_linkspec::{CircTarget, IntoOwnedChanTarget, OwnedChanTarget};
use tor_proto::{
    circuit::{handshake, CircParameters, CircuitBinding, ClientCirc, Path, UniqId},
    stream::{DataStream, StreamParameters},
    HopNum, TargetHop,
};

use crate::{Error, Result};

// The tunnel base methods. This MUST be derived on all tunnel types.
define_derive_deftly! {
    BaseTunnel for struct:

    impl From<tor_proto::ClientTunnel> for $ttype {
        fn from(tunnel: tor_proto::ClientTunnel) -> Self {
            Self { tunnel: Arc::new(tunnel) }
        }
    }

    impl From<Arc<tor_proto::ClientTunnel>> for $ttype {
        fn from(tunnel: Arc<tor_proto::ClientTunnel>) -> Self {
            Self { tunnel }
        }
    }

    impl $ttype {
        /// Return a reference to the underlying tunnel.
        ///
        /// Note: The "tunnel" name is hardcoded here. If it becomes an annoyance, we could make it
        /// as a meta value of the deftly declaration.
        fn tunnel_ref(&self) -> &Arc<tor_proto::ClientTunnel> {
            &self.tunnel
        }

        /// Return true if this tunnel is closed and therefore unusable.
        pub fn is_closed(&self) -> bool {
            self.tunnel_ref().is_closed()
        }

        /// Shutdown the tunnel meaning this sends a shutdown command to the underlying circuit
        /// reactor which will stop asynchronously.
        ///
        /// Note that it is not necessary to use this method as in if the tunnel reference is
        /// dropped, the circuit will close automatically.
        pub fn terminate(&self) {
            self.tunnel_ref().terminate();
        }

        /// Return a process-unique identifier for this tunnel.
        pub fn unique_id(&self) -> UniqId {
            self.tunnel_ref().unique_id()
        }

        /// Send raw message.
        pub async fn send_raw_msg(&self, _msg: AnyRelayMsg, _hop: TargetHop) -> Result<()> {
            /*
            self.tunnel_ref()
                .send_raw_msg(msg, hop)
                .await
                .map_err(|error| Error::Protocol {
                    action: "send raw msg",
                    peer: None,
                    error,
                    unique_id: Some(self.tunnel.unique_id()),
                })
            */
            todo!();
        }

        // TODO(conflux): Put start_conversation() here as it is all tunnel that can be conversed
        // with. This needs to be done in another commit because it requires refactoring
        // Conversation object to take a ClientTunnel which means that we need to change all
        // subsystems using Conversation (onion services mostly).

        // TODO(conflux): mq_account() is not needed because it is only used internally in a ClientCirc
        // in order to open streams. It might be the case that we need at some point to get the
        // CircuitAccount(s) from a tunnel. We would need then to either have a TunnelAccount or return
        // a Vec<CircuitAccount>.
    }
}

// Methods for a single path tunnel.
define_derive_deftly! {
    SinglePathTunnel for struct:

    impl $ttype {
        /// Return a reference to the circuit of this tunnel.
        fn circuit(&self) -> Result<&ClientCirc> {
            Ok(self.tunnel_ref()
                .as_single_circ()
                .map_err(|e| internal!("Non single path in a single path tunnel: {}", e))?)
        }

        /// Extend the circuit to a new target last hop using the ntor v3 handshake.
        ///
        /// TODO: Might want to pass which handshake type as a parameter so this function can be a
        /// catch all on all possible handshakes. For now, use ntor v3 for all the things.
        pub async fn extend<T: CircTarget>(&self, target: &T, params: CircParameters) -> Result<()> {
            self.circuit()?
                .extend(target, params)
                .await
                .map_err(|error| Error::Protocol {
                    action: "extend tunnel",
                    peer: Some(target.to_owned().to_logged()),
                    error,
                    unique_id: Some(self.tunnel.unique_id()),
                })
        }

        /// Return a Path object describing all the hops in this circuit.
        pub async fn path_ref(&self) -> Result<Arc<Path>> {
            self.tunnel_ref().path_ref()
                .map_err(|error| Error::Protocol {
                    action: "path ref",
                    peer: None,
                    error,
                    unique_id: Some(self.tunnel.unique_id()),
                })
        }
    }
}

// Methods for a multi path tunnel.
define_derive_deftly! {
    MultiPathTunnel for struct:

    impl $ttype {
        /// Return a Path object describing all the hops in this circuit.
        // TODO(conflux): Need to have this implemented on the ClientTunnel.
        pub async fn path_ref(&self) -> Result<Vec<Path>> {
            todo!()
        }
    }
}

// Methods for a tunnel that can transmit data (BEGIN).
define_derive_deftly! {
    DataTunnel for struct:

    impl $ttype {
        /// Start a stream to the given address and port, using a BEGIN cell.
        ///
        /// The use of a string for the address is intentional: you should let
        /// the remote Tor relay do the hostname lookup for you.
        pub async fn begin_stream(
            &self,
            target: &str,
            port: u16,
            params: Option<StreamParameters>,
        ) -> Result<DataStream> {
            self.tunnel_ref()
                .begin_stream(target, port, params)
                .await
                .map_err(|error| Error::Protocol {
                    action: "begin stream",
                    peer: None,
                    error,
                    unique_id: Some(self.tunnel.unique_id()),
                })
        }
    }

}

// Methods for a tunnel that can do DNS resolution (RESOLVE).
define_derive_deftly! {
    DnsTunnel for struct:

    impl $ttype {
        /// Perform a DNS lookup, using a RESOLVE cell with the last relay in this circuit.
        ///
        /// Note that this function does not check for timeouts; that's the caller's responsibility.
        pub async fn resolve(&self, hostname: &str) -> Result<Vec<IpAddr>> {
            self.tunnel_ref()
                .resolve(hostname)
                .await
                .map_err(|error| Error::Protocol {
                    action: "resolve",
                    peer: None,
                    error,
                    unique_id: Some(self.tunnel.unique_id()),
                })
        }

        /// Perform a reverse DNS lookup, using a RESOLVE cell with the last relay in this circuit.
        ///
        /// Note that this function does not check for timeouts; that's the caller's responsibility.
        pub async fn resolve_ptr(&self, addr: IpAddr) -> Result<Vec<String>> {
            self.tunnel_ref()
                .resolve_ptr(addr)
                .await
                .map_err(|error| Error::Protocol {
                    action: "resolve PTR",
                    peer: None,
                    error,
                    unique_id: Some(self.tunnel.unique_id()),
                })
        }
    }
}

// Methods for a tunnel that can do directory requests (BEGIN_DIR).
define_derive_deftly! {
    DirTunnel for struct:

    impl $ttype {
        /// Start a stream to the given address and port, using a BEGIN_DIR cell.
        pub async fn begin_dir_stream(&self) -> Result<DataStream> {
            self.tunnel_ref().clone()
                .begin_dir_stream()
                .await
                .map_err(|error| Error::Protocol {
                    action: "begin dir stream",
                    peer: None,
                    error,
                    unique_id: Some(self.tunnel.unique_id()),
                })
        }
    }
}

// Methods for a tunnel that can transmit data (BEGIN).
define_derive_deftly! {
    OnionServiceDataTunnel for struct:

    impl $ttype {
        /// Extend this circuit by a single, "virtual" hop.
        ///
        /// A virtual hop is one for which we do not add an actual network connection
        /// between separate hosts (such as Relays).  We only add a layer of
        /// cryptography.
        ///
        /// This is used to implement onion services: the client and the service
        /// both build a circuit to a single rendezvous point, and tell the
        /// rendezvous point to relay traffic between their two circuits.  Having
        /// completed a [`handshake`] out of band[^1], the parties each extend their
        /// circuits by a single "virtual" encryption hop that represents their
        /// shared cryptographic context.
        ///
        /// Once a circuit has been extended in this way, it is an error to try to
        /// extend it in any other way.
        ///
        /// [^1]: Technically, the handshake is only _mostly_ out of band: the
        ///     client sends their half of the handshake in an ` message, and the
        ///     service's response is inline in its `RENDEZVOUS2` message.
        //
        // TODO hs: let's try to enforce the "you can't extend a circuit again once
        // it has been extended this way" property.  We could do that with internal
        // state, or some kind of a type state pattern.
        //
        // TODO hs: possibly we should take a set of Protovers, and not just `Params`.
        pub async fn extend_virtual(
            &self,
            protocol: handshake::RelayProtocol,
            role: handshake::HandshakeRole,
            seed: impl handshake::KeyGenerator,
            params: CircParameters,
            capabilities: &tor_protover::Protocols,
        ) -> Result<()> {
            self.circuit()?
                .extend_virtual(protocol, role, seed, &params, capabilities)
                .await
                .map_err(|error| Error::Protocol {
                    action: "extend virtual tunnel",
                    peer: None,
                    error,
                    unique_id: Some(self.tunnel.unique_id()),
                })
        }
    }

}

/// A client single path data tunnel.
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, DataTunnel, DnsTunnel, SinglePathTunnel)]
pub struct ClientDataTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A client directory tunnel. This is always single path.
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, DirTunnel, SinglePathTunnel)]
pub struct ClientDirTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A client onion service single path data tunnel.
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, DataTunnel, OnionServiceDataTunnel, SinglePathTunnel)]
pub struct ClientOnionServiceDataTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A client onion service directory tunnel (to an HSDir). This is always single path.
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, DirTunnel, SinglePathTunnel)]
pub struct ClientOnionServiceDirTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A client onion service introduction tunnel. This is always single path.
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, SinglePathTunnel)]
pub struct ClientOnionServiceIntroTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A service onion service single path data tunnel.
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, DataTunnel, OnionServiceDataTunnel, SinglePathTunnel)]
pub struct ServiceOnionServiceDataTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A service onion service directory tunnel (to an HSDir). This is always single path.
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, DirTunnel, SinglePathTunnel)]
pub struct ServiceOnionServiceDirTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A service onion service introduction tunnel. This is always single path.
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, SinglePathTunnel)]
pub struct ServiceOnionServiceIntroTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A client multi path data tunnel (Conflux).
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, DataTunnel, DnsTunnel, MultiPathTunnel)]
pub struct ClientMultiPathDataTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A client multi path onion service data tunnel (Conflux, Rendeszvous).
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, DataTunnel, MultiPathTunnel)]
pub struct ClientMultiPathOnionServiceDataTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

/// A service multi path onion service data tunnel (Conflux, Rendeszvous).
#[derive(Deftly)]
#[derive_deftly(BaseTunnel, DataTunnel, MultiPathTunnel)]
pub struct ServiceMultiPathOnionServiceDataTunnel {
    /// The protocol level tunnel.
    tunnel: Arc<tor_proto::ClientTunnel>,
}

impl ClientDirTunnel {
    /// Return a description of the first hop of this circuit.
    pub async fn first_hop(&self) -> Result<OwnedChanTarget> {
        Ok(self
            .tunnel_ref()
            .first_hop()
            .map_err(|e| internal!("Bug getting dir tunnel first hop: {}", e))?)
    }
}

impl ServiceOnionServiceDataTunnel {
    /// Tell this tunnel to begin allowing the final hop of the tunnel to try
    /// to create new Tor streams, and to return those pending requests in an
    /// asynchronous stream.
    ///
    /// Ordinarily, these requests are rejected.
    ///
    /// There can only be one [`Stream`](futures::Stream) of this type created on a given tunnel.
    /// If a such a [`Stream`](futures::Stream) already exists, this method will return
    /// an error.
    ///
    /// After this method has been called on a tunnel, the tunnel is expected
    /// to receive requests of this type indefinitely, until it is finally closed.
    /// If the `Stream` is dropped, the next request on this tunnel will cause it to close.
    ///
    /// Only onion services (and eventually) exit relays should call this
    /// method.
    //
    // TODO: Someday, we might want to allow a stream request handler to be
    // un-registered.  However, nothing in the Tor protocol requires it.
    // TODO(conflux): hop_num will become a TargetHop.
    pub async fn allow_stream_requests(
        &self,
        allow_commands: &[tor_cell::relaycell::RelayCmd],
        hop_num: HopNum,
        filter: impl tor_proto::stream::IncomingStreamRequestFilter,
    ) -> Result<impl futures::Stream<Item = tor_proto::stream::IncomingStream>> {
        self.tunnel_ref()
            .allow_stream_requests(allow_commands, hop_num, filter)
            .await
            .map_err(|error| Error::Protocol {
                action: "allow stream requests",
                peer: None,
                error,
                unique_id: Some(self.tunnel.unique_id()),
            })
    }
}

impl ServiceOnionServiceIntroTunnel {
    /// Return the cryptographic material used to prove knowledge of a shared
    /// secret with with `hop`.
    ///
    /// See [`CircuitBinding`] for more information on how this is used.
    ///
    /// Return None if we have no circuit binding information for the hop, or if
    /// the hop does not exist.
    pub fn binding_key(&self, hop: HopNum) -> Option<CircuitBinding> {
        self.circuit()
            .ok()
            .and_then(|circ| circ.binding_key(hop).ok().flatten())
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -68,7 +68,7 @@ pub use util::skew::ClockSkew;
pub use channel::params::ChannelPaddingInstructions;
pub use congestion::params as ccparams;
pub use crypto::cell::{HopNum, HopNumDisplay};
pub use tunnel::{circuit, ClientTunnel};
pub use tunnel::{circuit, ClientTunnel, HopLocation, TargetHop};

/// A Result type for this crate.
pub type Result<T> = std::result::Result<T, Error>;
Loading