Expand description
Module exposing the circuit reactor subsystem.
This module implements the new multi-reactor circuit subsystem.
The entry point of the reactor is Reactor::run, which launches the
reactor background tasks, and begins listening for inbound cells on the provided
inbound Tor channel.
§Architecture
Internally, the circuit reactor consists of multiple reactors, each running in a separate task:
StreamReactor(one per hop): handles all messages arriving to, and coming from the streams of a given hop. The ready stream messages are sent to theBackwardReactorForwardReactor: handles incoming cells arriving on the “inbound” Tor channel (towards the guard, if we are a client, or towards the client, if we are a relay). If we are a client, it moves stream messages towards the correspondingStreamReactor. If we are a relay, in addition to sending any stream messages to theStreamReactor, this reactor also moves cells in the forward direction (from the client towards the exit)BackwardReactor: writes cells to the “inbound” Tor channel: towards the client if we are a relay, or the towards the exit if we are a client.
If we are an exit relay, the cell flow looks roughly like this:
<stream_tx
MPSC (0)>
+--------------> FWD -------------------------+
| | |
| | |
| | |
| | v
relay BackwardReactorCmd StreamReactor
^ <MPSC (0)> |
| | |
| | |
| | |
| v |
+--------------- BWD <------------------------+
application stream data <stream_rx
MPSC (0)>
For a middle relay (the `StreamReactor` is omitted for brevity,
but middle relays can have one too, if leaky pipe is in use):
```text unrecognized cell
+--------------> FWD -------------------------+
| | |
| | |
| | |
| | v
client BackwardReactorCmd relay
or relay <MPSC (0)> |
^ | |
| | |
| | |
| | |
| v |
+--------------- BWD <------------------------+On the client-side the ForwardReactor reads cells from the Tor channel to the guard,
and the BackwardReactor writes to it.
+--------------- FWD <--------------------+
| | |
| | |
| | |
v | |
StreamReactor BackwardReactorCmd guard
| <MPSC (0)> ^
| | |
| | |
| | |
| v |
+--------------> BWD ---------------------+Client with leaky pipe (SR = StreamReactor):
+------------------------------+
| +--------------------+ | (1 MPSC TX per SR)
| | | |
| | +----------- FWD <------------------+
| | | | |
| | | | |
| | | | |
v v v | |
SR SR SR BackwardReactorCmd guard
(hop 4) (hop 3) (hop 2) <MPSC (0)> ^
| | | | |
| | | | |
| | | | |
| | | v |
| | | BWD -------------------+
| | | ^
| | | |
| | | | <stream_rx
| | | | MPSC (0)>
+-------+-------+-------------+The read and write ends of the inbound and outbound Tor channels are “split”,
such that each reactor holds an inbound_chan_rx stream (for reading)
and a inbound_chan_tx sink (for writing):
ForwardReactorholds the reading end of the inbound (coming from the client, if we are a relay, or coming from the guard, if we are a client) Tor channel, and the writing end of the outbound (towards the exit, if we are a middle relay) Tor channel, if there is oneBackwardReactorholds the reading end of the outbound channel, if there is one, and the writing end of the inbound channel, if there is one
§ForwardReactor
It handles forward cells, by delegating to the implementation-dependent
ForwardHandler::handle_forward_cell, which decides
whether the cell needs to be handled in ForwardReactor,
or in the ForwardHandler itself.
More concretely:
Legend: `F` = "forward reactor", `H` = "ForwardHandler"
| Message | Received in | Handled in | Description |
|-------------------|-------------|------------|----------------------------------------|
| DESTROY | F | H | Handled internally by the FowardHandler|
|-------------------|-------------|------------|----------------------------------------|
| PADDING_NEGOTIATE | F | H | Handled internally by the FowardHandler|
|-------------------|-------------|------------|----------------------------------------|
| *unrecognized* | F | H | Unrecognized relay cell handling is |
| RELAY OR | | | implementation-dependent so these are |
| RELAY_EARLY | | | handled in the ForwardHandler. |
| | | | |
| | | | The relay ForwardHandler will handle |
| | | | these by forwarding them to the next |
| | | | hop, if there is one. |
| | | | |
| | | | Clients don't yet implement |
| | | | ForwardHandler, but when they do, |
| | | | its implementation will simply reject |
| | | | any messages that can't be decrypted |
|-------------------|-------------|------------|----------------------------------------|
| *recognized* | F | see table | Handling depends on the cmd |
| RELAY OR | | below | |
| RELAY_EARLY | | | |Recognized relay cells are handled by splitting each cell into individual messages, and handling each message individually as described in the table below (Note: since prop340 is not yet implemented, in practice there is only 1 message per cell):
Legend: `F` = "forward reactor", `B` = "backward reactor", `S` = "stream reactor"
| RELAY cmd | Received in | Handled in | Description |
|-------------------|-------------|------------|----------------------------------------|
| SENDME | F | B | Sent to BackwardReactor for handling |
| | | | (BackwardReactorCmd::HandleSendme) |
| | | | because the forward reactor doesn't |
| | | | have access to the inbound_chan_tx part|
| | | | of the inbound (towards the client) |
| | | | Tor channel, and so cannot obtain the |
| | | | congestion signals needed for SENDME |
| | | | handling |
|-------------------|-------------|------------|----------------------------------------|
| Other | F | F | Passed to impl-dependent handler |
| (StreamId = 0) | | | `ForwardHandler::handle_meta_msg()` |
|-------------------|-------------|------------|----------------------------------------|
| Other | F | S | All messages with a non-zero stream ID |
| (StreamId != 0) | | | are forwarded to the stream reactor |
|-------------------|-------------|------------|----------------------------------------|§BackwardReactor
It handles
- the packaging and delivery of all cells that need to be written to the “inbound” Tor channel (it writes them to the towards-the-client Tor channel sink) (partially implemented)
- incoming cells coming over the “outbound” Tor channel. This channel only exists if we are a middle relay. These cells are relayed to the “inbound” Tor channel (not implemented).
- the sending of padding cells, according to the PaddingController’s instructions
This multi-reactor architecture should, in theory, have better performance than a single reactor system, because it enables us to parallelize some of the work: the forward and backward directions share little state, because they read from, and write to, different sinks/streams, so they can be run in parallel (as separate tasks). With a single reactor architecture, the reactor would need to drive both the forward and the backward direction, and on each iteration would need to decide which to prioritize, which might prove tricky (though prioritizing one of them at random would’ve probably been good enough).
The monolithic single reactor alternative would also have been significantly more convoluted, and so more difficult to maintain in the long run.
Modules§
- backward 🔒
- A circuit’s view of the backward state of the circuit.
- circhop 🔒
- Module exposing structures relating to the reactor’s view of a circuit’s hops.
- forward 🔒
- A circuit’s view of the forward state of the circuit.
- hop_mgr 🔒
- Channel for sending messages to
StreamReactor. - macros 🔒
- Helper macros for the circuit reactors.
- stream 🔒
- The stream reactor.
Structs§
- Circ
Reactor 🔒Handle - A handle for interacting with a circuit reactor.
- Reactor 🔒
- The entry point of the circuit reactor subsystem.
- Reactor
Ctrl 🔒 - A handle for sending control/command messages to a FWD or BWD.
Enums§
Traits§
- Control
Handler 🔒 - Trait implemented by types that can handle control messages and commands.
Type Aliases§
- Reactor
Result 🔒Channel - The type of a oneshot channel used to inform reactor of the result of an operation.