tor_proto/circuit.rs
1//! Circuit-related types and helpers.
2//!
3//! This code is shared between the client and relay implementations.
4
5pub(crate) mod cell_sender;
6pub(crate) mod celltypes;
7pub(crate) mod circ_sender;
8pub(crate) mod circhop;
9pub(crate) mod create;
10pub(crate) mod padding;
11pub(crate) mod reactor;
12pub(crate) mod syncview;
13pub(crate) mod unique_id;
14
15pub use crate::memquota::StreamAccount;
16pub use syncview::CircHopSyncView;
17pub use unique_id::UniqId;
18
19use crate::ccparams::CongestionControlParams;
20use crate::stream::flow_ctrl::params::FlowCtrlParameters;
21use tor_cell::relaycell::extend::SubprotocolRequest;
22use tor_error::ErrorKind;
23use tor_protover::Protocols;
24
25pub(crate) use circ_sender::{CircuitRxReceiver, CircuitRxSender};
26
27/// Estimated upper bound for the likely number of hops.
28pub(crate) const HOPS: usize = 6;
29
30/// Description of the network's current rules for building circuits.
31///
32/// This type describes rules derived from the consensus,
33/// and possibly amended by our own configuration.
34///
35/// Typically, this type created once for an entire circuit,
36/// and any special per-hop information is derived
37/// from each hop as a CircTarget.
38/// Note however that callers _may_ provide different `CircParameters`
39/// for different hops within a circuit if they have some reason to do so,
40/// so we do not enforce that every hop in a circuit has the same `CircParameters`.
41#[non_exhaustive]
42#[derive(Clone, Debug)]
43pub struct CircParameters {
44 /// Whether we should include ed25519 identities when we send
45 /// EXTEND2 cells.
46 pub extend_by_ed25519_id: bool,
47 /// Congestion control parameters for this circuit.
48 pub ccontrol: CongestionControlParams,
49
50 /// Flow control parameters to use for all streams on this circuit.
51 // While flow control is a stream property and not a circuit property,
52 // and it may seem better to pass the flow control parameters to for example `begin_stream()`,
53 // it's included in [`CircParameters`] for the following reasons:
54 //
55 // - When endpoints (exits + hs) receive new stream requests, they need the flow control
56 // parameters immediately. It would be easy to pass flow control parameters when creating a
57 // stream, but it's not as easy to get flow control parameters when receiving a new stream
58 // request, unless those parameters are already available to the circuit (like
59 // `CircParameters` are).
60 // - It's unclear if new streams on existing circuits should switch to new flow control
61 // parameters if the consensus changes. This behaviour doesn't appear to be specified. It
62 // might also leak information to the circuit's endpoint about when we downloaded new
63 // directory documents. So it seems best to stick with the same flow control parameters for
64 // the lifetime of the circuit.
65 // - It doesn't belong in [`StreamParameters`] as `StreamParameters` is a set of preferences
66 // with defaults, and consensus parameters aren't preferences and don't have defaults.
67 // (Technically they have defaults, but `StreamParameters` isn't the place to set them.)
68 pub flow_ctrl: FlowCtrlParameters,
69
70 /// Maximum number of permitted incoming relay cells for each hop.
71 ///
72 /// If we would receive more relay cells than this from a single hop,
73 /// we close the circuit with [`ExcessInboundCells`](crate::Error::ExcessInboundCells).
74 ///
75 /// If this value is None, then there is no limit to the number of inbound cells.
76 ///
77 /// Known limitation: If this value if `u32::MAX`,
78 /// then a limit of `u32::MAX - 1` is enforced.
79 pub n_incoming_cells_permitted: Option<u32>,
80
81 /// Maximum number of permitted outgoing relay cells for each hop.
82 ///
83 /// If we would try to send more relay cells than this from a single hop,
84 /// we close the circuit with [`ExcessOutboundCells`](crate::Error::ExcessOutboundCells).
85 /// It is the circuit-user's responsibility to make sure that this does not happen.
86 ///
87 /// This setting is used to ensure that we do not violate a limit
88 /// imposed by `n_incoming_cells_permitted`
89 /// on the other side of a circuit.
90 ///
91 /// If this value is None, then there is no limit to the number of outbound cells.
92 ///
93 /// Known limitation: If this value if `u32::MAX`,
94 /// then a limit of `u32::MAX - 1` is enforced.
95 pub n_outgoing_cells_permitted: Option<u32>,
96}
97
98tor_protover::subprotocol_restricted_set! {
99 /// The enabled/disabled status of subprotocols that are allowed to be requested through a
100 /// subprotocol request during a circuit handshake.
101 ///
102 /// The allowed subprotocols are defined in:
103 /// <https://spec.torproject.org/tor-spec/create-created-cells.html#subproto-request>
104 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
105 pub(crate) struct HandshakeSubprotocols {
106 RELAY_CRYPT_CGO,
107 }
108}
109
110impl HandshakeSubprotocols {
111 /// Build a [`HandshakeSubprotocols`] from a [`SubprotocolRequest`]
112 /// provided during a circuit handshake.
113 ///
114 /// If the `SubprotocolRequest` contains subprotocols that aren't
115 /// allowed to be requested through a subprotocol request,
116 /// this returns an error containing the original `SubprotocolRequest`.
117 //
118 // It would be nice to return a list of only the invalid subprotocols,
119 // but it seems a bit expensive to compute on the error path when we probably
120 // want to fail quickly.
121 pub(crate) fn try_from_request(
122 protos: SubprotocolRequest,
123 ) -> Result<Self, InvalidHandshakeSubprotocolError> {
124 use std::sync::LazyLock;
125 static ALL: LazyLock<Protocols> =
126 LazyLock::new(|| Protocols::from(HandshakeSubprotocols::ALL));
127
128 if !protos.contains_only(&ALL) {
129 return Err(InvalidHandshakeSubprotocolError(protos));
130 }
131
132 Ok(Self {
133 relay_crypt_cgo: protos.contains(tor_protover::named::RELAY_CRYPT_CGO),
134 })
135 }
136}
137
138/// The subprotocol request had subprotocols that are not all supported in circuit handshakes.
139///
140/// Contains the requested subprotocols (both valid and invalid).
141#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
142#[error("Request included subprotocols that we do not support in circuit handshakes: {0:?}")]
143pub(crate) struct InvalidHandshakeSubprotocolError(SubprotocolRequest);
144
145impl tor_error::HasKind for InvalidHandshakeSubprotocolError {
146 fn kind(&self) -> ErrorKind {
147 ErrorKind::TorProtocolViolation
148 }
149}
150
151#[cfg(test)]
152pub(crate) mod test {
153 use super::*;
154
155 #[cfg(feature = "relay")]
156 use crate::relay::{CircNetParameters, CongestionControlNetParams};
157
158 pub(crate) use super::circ_sender::test::fake_mpsc;
159
160 /// Return a new [`CircNetParameters`] using default values for unit tests. They are based on
161 /// consensus defaults but should not be considered to be accurate from the one used on the
162 /// production network.
163 #[cfg(feature = "relay")]
164 pub(crate) fn new_circ_net_params() -> CircNetParameters {
165 CircNetParameters {
166 cc: CongestionControlNetParams::defaults_for_tests(),
167 }
168 }
169
170 #[test]
171 fn handshake_subprotocols() {
172 let empty_iter: [tor_protover::NumberedSubver; 0] = [];
173 let request = SubprotocolRequest::from_iter(empty_iter);
174 assert_eq!(
175 HandshakeSubprotocols::try_from_request(request),
176 Ok(HandshakeSubprotocols {
177 relay_crypt_cgo: false,
178 }),
179 );
180
181 let request = SubprotocolRequest::from_iter([tor_protover::named::RELAY_CRYPT_CGO]);
182 assert_eq!(
183 HandshakeSubprotocols::try_from_request(request),
184 Ok(HandshakeSubprotocols {
185 relay_crypt_cgo: true,
186 }),
187 );
188
189 let request =
190 SubprotocolRequest::from_iter([tor_protover::named::RELAY_NEGOTIATE_SUBPROTO]);
191 assert!(HandshakeSubprotocols::try_from_request(request).is_err());
192
193 let request = SubprotocolRequest::from_iter([
194 tor_protover::named::RELAY_NEGOTIATE_SUBPROTO,
195 tor_protover::named::RELAY_CRYPT_CGO,
196 ]);
197 assert!(HandshakeSubprotocols::try_from_request(request).is_err());
198 }
199}