Skip to main content

Module reactor

Module reactor 

Source
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 the BackwardReactor
  • ForwardReactor: 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 corresponding StreamReactor. If we are a relay, in addition to sending any stream messages to the StreamReactor, 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):

  • ForwardReactor holds 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 one
  • BackwardReactor holds 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§

CircReactorHandle 🔒
A handle for interacting with a circuit reactor.
Reactor 🔒
The entry point of the circuit reactor subsystem.
ReactorCtrl 🔒
A handle for sending control/command messages to a FWD or BWD.

Enums§

CtrlCmd 🔒
A control command.
CtrlMsg 🔒
A control message.

Traits§

ControlHandler 🔒
Trait implemented by types that can handle control messages and commands.

Type Aliases§

ReactorResultChannel 🔒
The type of a oneshot channel used to inform reactor of the result of an operation.