netget/server/tor_relay/mod.rs
1//! Tor Relay (OR protocol) server - **partial, not interoperable with real Tor**.
2//!
3//! The link handshake stops after VERSIONS (no CERTS/AUTH_CHALLENGE/NETINFO), relay cell
4//! digests are neither computed nor verified, and there is no EXTEND. See `CLAUDE.md` in this
5//! directory for the full list of non-conformances. What is implemented:
6//! - **Length-driven cell framing**: VERSIONS (2-byte circuit id, variable length) and
7//! commands >= 128 are framed by their length field rather than assumed to be 514 bytes,
8//! and the read is cancellation-safe inside the `select!`
9//! - **ntor handshake** for circuit creation (CREATE2/CREATED2) with Curve25519 DH
10//! - **Relay cell encryption** using AES-128-CTR with forward/backward keys
11//! - **Circuit management** with crypto state and stream multiplexing
12//! - **Exit relay functionality** (BEGIN, DATA, END, CONNECTED)
13//! - **Bidirectional data forwarding** (TCP ↔ Tor client) via background tasks
14//! - **SENDME flow control** at circuit and stream levels (tor-spec compliant)
15//! - **Bandwidth tracking** per circuit and aggregate statistics
16//! - **Channel-based architecture** for concurrent stream handling
17//! - **LLM notification** on circuit creation and on unimplemented RELAY commands. The data
18//! path itself (BEGIN/DATA/END/SENDME, exit target selection) is decided in Rust, and no
19//! exit policy is enforced.
20//!
21//! ## Architecture
22//!
23//! ```text
24//! TLS Connection (TorRelaySession)
25//! │
26//! ├─ tokio::select! loop
27//! │ ├─ Read TLS stream → Parse cells
28//! │ └─ Write from channel ← Forwarder tasks
29//! │
30//! ├─ CircuitManager (shared across connections)
31//! │ ├─ Circuit 1 (crypto + streams)
32//! │ │ ├─ Stream 1 → TCP connection
33//! │ │ ├─ Stream 2 → TCP connection
34//! │ │ └─ Flow control windows
35//! │ ├─ Circuit 2 (crypto + streams)
36//! │ └─ Statistics tracking
37//! │
38//! └─ Background forwarder tasks (per stream)
39//! └─ TCP → Encrypt → Channel → TLS
40//! ```
41//!
42//! ## Cell Processing Flow
43//!
44//! 1. **CREATE2**: ntor handshake → CREATED2 response
45//! 2. **RELAY/BEGIN**: Parse target → TCP connect → CONNECTED response
46//! 3. **RELAY/DATA**: Decrypt → Forward to TCP (client→server) OR Encrypt ← TCP (server→client)
47//! 4. **RELAY/END**: Close TCP connection
48//! 5. **RELAY/SENDME**: Update flow control windows
49//!
50//! ## Flow Control (SENDME)
51//!
52//! - Circuit-level: 1000 cell window, SENDME every 100 cells
53//! - Stream-level: 500 cell window, SENDME every 50 cells
54//! - Automatic SENDME generation on receive thresholds
55//! - Package window prevents overwhelming next hop
56//!
57//! **Status**: Experimental. Not a usable relay; see `CLAUDE.md`.
58
59pub mod actions;
60pub mod circuit;
61pub mod stream;
62
63use anyhow::{Context, Result};
64use std::net::SocketAddr;
65use std::sync::Arc;
66use tokio::sync::{mpsc, Mutex};
67use tracing::{debug, error, info, trace, warn};
68
69#[cfg(unix)]
70use crate::llm::action_helper::call_llm;
71#[cfg(unix)]
72use crate::llm::ollama_client::OllamaClient;
73#[cfg(unix)]
74use crate::llm::ActionResult;
75#[cfg(unix)]
76use crate::protocol::Event;
77#[cfg(unix)]
78use crate::server::TorRelayProtocol;
79#[cfg(unix)]
80use crate::state::app_state::AppState;
81#[cfg(unix)]
82use actions::{TOR_RELAY_CIRCUIT_CREATED_EVENT, TOR_RELAY_RELAY_CELL_EVENT};
83#[cfg(unix)]
84use circuit::{CircuitId, CircuitManager, StreamId};
85#[cfg(unix)]
86use stream::{build_relay_cell, connect_to_target, end_reason, parse_begin_target, relay_command};
87
88#[cfg(unix)]
89use crate::logging::emit::Log;
90use tokio::io::{AsyncReadExt, AsyncWriteExt};
91use tokio::net::{TcpListener, TcpStream};
92use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
93use tokio_rustls::rustls::ServerConfig;
94use tokio_rustls::TlsAcceptor;
95
96/// The write half of a peer's TLS stream, shared between the session loop and the dashboard's
97/// peer-command task (`server::peer_support`).
98#[cfg(unix)]
99type SharedWrite = Arc<Mutex<tokio::io::WriteHalf<tokio_rustls::server::TlsStream<TcpStream>>>>;
100
101/// Tor Relay server - handles OR protocol connections
102pub struct TorRelayServer;
103
104#[cfg(unix)]
105impl TorRelayServer {
106 /// Spawn Tor Relay server with LLM action integration
107 pub async fn spawn_with_llm_actions(
108 listen_addr: SocketAddr,
109 llm_client: OllamaClient,
110 app_state: Arc<AppState>,
111 status_tx: mpsc::UnboundedSender<String>,
112 server_id: crate::state::ServerId,
113 ) -> Result<SocketAddr> {
114 // Generate self-signed TLS certificate for OR protocol
115 let (cert, key) = generate_tls_certificate()?;
116
117 // Configure TLS acceptor
118 // `ring`, which is the provider this tree compiles rustls with: `Cargo.toml` pins
119 // `rustls`/`tokio-rustls` to `default-features = false, features = ["ring", ...]`
120 // because the default `aws_lc_rs` is a C library that does not build for wasm32, and
121 // every other TLS site here (`tls`, `dot`, `proxy`, `openvpn`, the binary's own
122 // `install_default`) uses `ring` for that reason. Naming `aws_lc_rs` here did not
123 // select a different provider - it did not compile at all, so `--features tor` was a
124 // hard build error on its own and only ever got as far as a feature set that happened
125 // to turn the `aws_lc_rs` feature back on.
126 let crypto_provider = tokio_rustls::rustls::crypto::ring::default_provider();
127 let tls_config = ServerConfig::builder_with_provider(Arc::new(crypto_provider))
128 // TLS 1.2 as well as 1.3. A real Tor relay accepts both -- the link
129 // specification's minimum is 1.2 -- and restricted to 1.3 alone this server
130 // rejected every peer that sent a 1.2 ClientHello with
131 // `SupportedVersionsExtensionRequired`, which is rustls saying "this hello has
132 // no supported_versions extension" and reads like a peer bug rather than our
133 // own configuration.
134 .with_protocol_versions(&[
135 &tokio_rustls::rustls::version::TLS13,
136 &tokio_rustls::rustls::version::TLS12,
137 ])
138 .expect("Valid TLS protocol versions")
139 .with_no_client_auth()
140 .with_single_cert(vec![cert], key)
141 .context("Failed to create TLS config")?;
142
143 let acceptor = TlsAcceptor::from(Arc::new(tls_config));
144
145 // Bind TCP listener
146 let listener = TcpListener::bind(listen_addr)
147 .await
148 .context("Failed to bind Tor Relay server")?;
149
150 let actual_addr = listener
151 .local_addr()
152 .context("Failed to get local address")?;
153
154 Log::new(Some(&status_tx)).info(format!(
155 "Tor Relay (OR protocol) server listening on {}",
156 actual_addr
157 ));
158
159 // Create circuit manager (shared across all connections)
160 let circuit_manager = Arc::new(CircuitManager::new());
161
162 // Publish the relay identity.
163 //
164 // The ntor handshake mixes the relay's identity fingerprint (ID) and its onion
165 // public key (B) into `secret_input`, so a peer that does not know both cannot
166 // derive the same key material — it gets a CREATED2 it can do nothing with. A real
167 // relay publishes them in its router descriptor. This one generates a fresh onion
168 // key per process and used to print only the fingerprint, which meant **no peer
169 // could ever complete a circuit with it**. Both are public values by design.
170 let fingerprint = circuit_manager.identity_fingerprint();
171 let onion_key = *circuit_manager.onion_public_key().as_bytes();
172 Log::new(Some(&status_tx)).info(format!("Relay fingerprint: {}", hex::encode(fingerprint)));
173 Log::new(Some(&status_tx)).info(format!("Relay onion key: {}", hex::encode(onion_key)));
174
175 let protocol = Arc::new(TorRelayProtocol::new());
176
177 // Spawn connection handler
178 let task_registrar = app_state.clone();
179 let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONNECTIONS);
180 let accept_handle = tokio::spawn(async move {
181 loop {
182 match crate::server::accept_bounded::accept_bounded(
183 &listener,
184 &limiter,
185 CONNECTION_CAP_REFUSAL,
186 "Tor Relay",
187 Some(&status_tx),
188 )
189 .await
190 {
191 Ok((stream, remote_addr, permit)) => {
192 let connection_id = crate::server::connection::ConnectionId::new(
193 app_state.get_next_unified_id().await,
194 );
195 Log::new(Some(&status_tx)).debug(format!(
196 "Tor Relay connection {} from {}",
197 connection_id, remote_addr
198 ));
199
200 let llm_clone = llm_client.clone();
201 let state_clone = app_state.clone();
202 let status_clone = status_tx.clone();
203 let acceptor_clone = acceptor.clone();
204 let protocol_clone = protocol.clone();
205 let circuit_mgr_clone = circuit_manager.clone();
206
207 // Tracked, not detached: stop_server must abort this task too.
208 let task_owner = app_state.clone();
209 task_owner
210 .spawn_server_task(server_id, async move {
211 // Held for the life of the connection, so the cap counts live
212 // peers rather than accepts.
213 let _permit = permit;
214 if let Err(e) = handle_tor_relay_connection(
215 stream,
216 connection_id,
217 server_id,
218 remote_addr,
219 acceptor_clone,
220 llm_clone,
221 state_clone,
222 status_clone,
223 protocol_clone,
224 circuit_mgr_clone,
225 )
226 .await
227 {
228 error!("Tor Relay connection error: {}", e);
229 }
230 })
231 .await;
232 }
233 Err(e) => {
234 Log::new(Some(&status_tx))
235 .error(format!("Failed to accept Tor Relay connection: {}", e));
236 }
237 }
238 }
239 });
240
241 task_registrar
242 .register_server_task(server_id, accept_handle)
243 .await;
244
245 Ok(actual_addr)
246 }
247}
248
249#[cfg(not(unix))]
250impl TorRelayServer {
251 pub async fn spawn_with_llm_actions(
252 _listen_addr: SocketAddr,
253 _llm_client: OllamaClient,
254 _app_state: Arc<AppState>,
255 _status_tx: mpsc::UnboundedSender<String>,
256 _server_id: crate::state::ServerId,
257 ) -> Result<SocketAddr> {
258 anyhow::bail!("Tor Relay feature not enabled")
259 }
260}
261
262/// Generate self-signed TLS certificate for OR protocol
263fn generate_tls_certificate() -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>)> {
264 use rcgen::{CertificateParams, KeyPair};
265
266 let mut params = CertificateParams::new(vec!["tor-relay.local".to_string()])?;
267 params.distinguished_name = rcgen::DistinguishedName::new();
268 params
269 .distinguished_name
270 .push(rcgen::DnType::CommonName, "NetGet Tor Relay");
271
272 let key_pair = KeyPair::generate()?;
273 let cert = params.self_signed(&key_pair)?;
274
275 let cert_der = CertificateDer::from(cert.der().to_vec());
276 let key_der = PrivateKeyDer::Pkcs8(key_pair.serialize_der().into());
277
278 Ok((cert_der, key_der))
279}
280
281/// Handle individual Tor Relay connection.
282///
283/// Owns the connection's bookkeeping: the `AppState` entry the dashboard draws the peer row
284/// from, and the peer handle that row's `[ message ]` / `[ disconnect ]` buttons need. Both
285/// are released here on **every** exit, including a failed TLS handshake, so nothing is left
286/// listed as live after the socket has gone.
287#[allow(clippy::too_many_arguments)]
288async fn handle_tor_relay_connection(
289 stream: TcpStream,
290 connection_id: crate::server::connection::ConnectionId,
291 server_id: crate::state::ServerId,
292 remote_addr: SocketAddr,
293 acceptor: TlsAcceptor,
294 llm_client: OllamaClient,
295 app_state: Arc<AppState>,
296 status_tx: mpsc::UnboundedSender<String>,
297 protocol: Arc<TorRelayProtocol>,
298 circuit_manager: Arc<CircuitManager>,
299) -> Result<()> {
300 // Track the connection. Nothing did this before, so a relay with peers on it showed an
301 // empty peer list, no byte counters, and no row for the dashboard to hang a
302 // "[ message this peer ]" button on.
303 let local_addr = stream
304 .local_addr()
305 .unwrap_or_else(|_| "0.0.0.0:0".parse().expect("0.0.0.0:0 parses"));
306 let now = crate::utils::clock::Instant::now();
307 app_state
308 .add_connection_to_server(
309 server_id,
310 crate::state::server::ConnectionState {
311 id: connection_id,
312 remote_addr,
313 local_addr,
314 bytes_sent: 0,
315 bytes_received: 0,
316 packets_sent: 0,
317 packets_received: 0,
318 last_activity: now,
319 status: crate::state::server::ConnectionStatus::Active,
320 status_changed_at: now,
321 protocol_info: crate::state::server::ProtocolConnectionInfo::empty(),
322 },
323 )
324 .await;
325 let _ = status_tx.send("__UPDATE_UI__".to_string());
326
327 let result = run_tor_relay_connection(
328 stream,
329 connection_id,
330 server_id,
331 remote_addr,
332 acceptor,
333 llm_client,
334 app_state.clone(),
335 status_tx.clone(),
336 protocol,
337 circuit_manager,
338 )
339 .await;
340
341 // Every exit path lands here - a TLS handshake that never completed, a clean close, a cell
342 // error that returned `Err`, an injected disconnect. Idempotent with the peer task's own
343 // close path.
344 app_state
345 .remove_peer_handle(server_id, connection_id.as_u32())
346 .await;
347 app_state
348 .update_connection_status(
349 server_id,
350 connection_id,
351 crate::state::server::ConnectionStatus::Closed,
352 )
353 .await;
354 let _ = status_tx.send("__UPDATE_UI__".to_string());
355
356 result
357}
358
359/// The TLS handshake and the session itself. Split out so its `?` paths cannot skip the
360/// teardown in [`handle_tor_relay_connection`].
361#[allow(clippy::too_many_arguments)]
362async fn run_tor_relay_connection(
363 stream: TcpStream,
364 connection_id: crate::server::connection::ConnectionId,
365 server_id: crate::state::ServerId,
366 remote_addr: SocketAddr,
367 acceptor: TlsAcceptor,
368 llm_client: OllamaClient,
369 app_state: Arc<AppState>,
370 status_tx: mpsc::UnboundedSender<String>,
371 protocol: Arc<TorRelayProtocol>,
372 circuit_manager: Arc<CircuitManager>,
373) -> Result<()> {
374 // Perform TLS handshake
375 let tls_stream = match acceptor.accept(stream).await {
376 Ok(s) => {
377 Log::new(Some(&status_tx)).info(format!("TLS handshake completed for {}", remote_addr));
378 s
379 }
380 Err(e) => {
381 // Handshake failure is a client-side condition, not a server error: WARN.
382 Log::new(Some(&status_tx))
383 .warn(format!("TLS handshake failed for {}: {}", remote_addr, e));
384 return Err(e.into());
385 }
386 };
387
388 // Split the TLS stream (never clone it). The reader stays with the session's `select!`
389 // loop; the write half is shared with the peer-command task through an `Arc<Mutex<..>>`,
390 // which is the only lock either side takes and is never held across an `.await` that does
391 // anything but the write itself. Note the difference from `spawn_stream_forwarder` below:
392 // there a lock is held over a whole *exit* TcpStream whose task parks in `read()`, which
393 // is why only the read half is taken there. Here nothing reads through the mutex.
394 let (reader, writer) = tokio::io::split(tls_stream);
395 let write_half: SharedWrite = Arc::new(Mutex::new(writer));
396
397 // Peer messaging: registered BEFORE the first cell is read. A relay says nothing until the
398 // peer sends VERSIONS, and a manual `*` rule parks the events this connection raises for
399 // minutes - the operator has to be able to reach, or hang up, a connection that is waiting
400 // on their own answer.
401 let peer_rx = crate::server::peer_support::register_peer_channel(
402 &app_state,
403 server_id,
404 connection_id.as_u32(),
405 )
406 .await;
407 crate::server::peer_support::spawn_peer_command_task(
408 peer_rx,
409 protocol.clone(),
410 app_state.clone(),
411 server_id,
412 connection_id.as_u32(),
413 write_half.clone(),
414 status_tx.clone(),
415 );
416
417 // Create channel for outgoing cells
418 let (outgoing_tx, outgoing_rx) = mpsc::unbounded_channel();
419
420 let mut session = TorRelaySession {
421 reader,
422 write_half: write_half.clone(),
423 connection_id,
424 server_id,
425 remote_addr,
426 llm_client,
427 app_state,
428 status_tx,
429 protocol,
430 circuit_manager,
431 outgoing_tx,
432 outgoing_rx,
433 };
434
435 let result = session.handle().await;
436
437 // Make the FIN immediate rather than waiting for the peer task to drop its clone.
438 let _ = write_half.lock().await.shutdown().await;
439 result
440}
441
442/// Tor Relay session handler
443struct TorRelaySession {
444 /// Read half of the TLS stream, owned outright by the `select!` loop.
445 reader: tokio::io::ReadHalf<tokio_rustls::server::TlsStream<TcpStream>>,
446 /// Write half, shared with the dashboard's peer-command task.
447 write_half: SharedWrite,
448 connection_id: crate::server::connection::ConnectionId,
449 server_id: crate::state::ServerId,
450 remote_addr: SocketAddr,
451 llm_client: OllamaClient,
452 app_state: Arc<AppState>,
453 status_tx: mpsc::UnboundedSender<String>,
454 protocol: Arc<TorRelayProtocol>,
455 circuit_manager: Arc<CircuitManager>,
456 /// Channel for sending outgoing cells (from forwarder tasks)
457 outgoing_tx: mpsc::UnboundedSender<Vec<u8>>,
458 outgoing_rx: mpsc::UnboundedReceiver<Vec<u8>>,
459}
460
461impl TorRelaySession {
462 /// Handle Tor Relay session - read cells and process.
463 ///
464 /// Two properties this loop has to hold, both of which it used to break:
465 ///
466 /// 1. **Framing follows the cell, not a fixed size.** It used to `read_exact` into a
467 /// 514-byte buffer, so the first thing a real Tor client sends - an 11-byte VERSIONS
468 /// cell - blocked it forever and `tor` logged `died in state handshaking`. Any
469 /// variable-length cell (VERSIONS, or command >= 128) did the same.
470 /// 2. **The read is cancellation-safe.** `read_exact` is not: dropping it mid-cell when
471 /// the `outgoing_rx` branch of the `select!` wins discards the bytes already taken
472 /// off the TLS stream, permanently desynchronising the connection. `read` is
473 /// cancel-safe, so bytes are accumulated in `inbuf` and cells are framed out of it.
474 async fn handle(&mut self) -> Result<()> {
475 debug!("Tor Relay session started for {}", self.remote_addr);
476
477 let mut inbuf: Vec<u8> = Vec::with_capacity(4096);
478 let mut read_buf = vec![0u8; 4096];
479 // False until the peer has sent anything at all; see FIRST_CELL_READ_TIMEOUT.
480 let mut seen_cell = false;
481 // Only the first cell of a connection may be a VERSIONS cell, and only a VERSIONS
482 // cell uses a 2-byte circuit id (tor-spec 3). After it, framing is unambiguous.
483 let mut expecting_versions = true;
484
485 loop {
486 // Drain every complete cell already buffered before waiting for more bytes.
487 while let Some(cell) = take_cell(&mut inbuf, expecting_versions) {
488 expecting_versions = false;
489
490 match cell {
491 FramedCell::Variable {
492 command, payload, ..
493 } => {
494 if let Some(response) = self.handle_variable_cell(command, &payload) {
495 self.write_counted(&response).await?;
496 }
497 }
498 FramedCell::Fixed { circuit_id, raw } => {
499 let Some(cell_info) = parse_tor_cell(&raw) else {
500 warn!("Failed to parse Tor cell from {}", self.remote_addr);
501 continue;
502 };
503 debug!(
504 "Tor cell: type={}, circuit_id={}",
505 cell_info.cell_type, cell_info.circuit_id
506 );
507
508 match self.handle_cell(cell_info, &raw).await {
509 Ok(Some(response)) => {
510 self.write_counted(&response).await?;
511 debug!("Sent response cell ({} bytes)", response.len());
512 }
513 Ok(None) => {}
514 Err(e) => {
515 error!("Failed to handle cell: {}", e);
516 let destroy = self.create_destroy_cell(circuit_id);
517 self.write_counted(&destroy).await?;
518 return Err(e);
519 }
520 }
521 }
522 }
523 }
524
525 // Re-arm the idle deadline for the wait that follows. It is set here, immediately
526 // before the `select!`, rather than kept running across the loop body — and that is
527 // deliberate: `handle_cell` above awaits the model, and may park the cell for a
528 // human at the dashboard (`src/state/intercepts.rs`, 300s by default). Arming the
529 // clock only over the wait means none of that time is ever counted as idle, so an
530 // answer that takes minutes cannot close the connection it is an answer for.
531 let idle_bound = if seen_cell {
532 IDLE_BETWEEN_CELLS_TIMEOUT
533 } else {
534 FIRST_CELL_READ_TIMEOUT
535 };
536 let idle_deadline = tokio::time::sleep(idle_bound);
537 tokio::pin!(idle_deadline);
538
539 tokio::select! {
540 // Nothing at all from the peer, and nothing to send it, for the whole bound.
541 _ = &mut idle_deadline => {
542 Log::new(Some(&self.status_tx)).info(format!(
543 "Tor Relay peer {} sent nothing for {}s; closing idle connection",
544 self.remote_addr,
545 idle_bound.as_secs()
546 ));
547 return Ok(());
548 }
549
550 // Read incoming bytes from the TLS stream. `read` is cancel-safe: if the
551 // other branch wins, nothing has been consumed.
552 read_result = self.reader.read(&mut read_buf) => {
553 match read_result {
554 Ok(0) => {
555 Log::new(Some(&self.status_tx)).info(format!(
556 "Tor Relay connection closed by {}",
557 self.remote_addr
558 ));
559 return Ok(());
560 }
561 Ok(n) => {
562 seen_cell = true;
563 trace!("Received {} bytes from {}", n, self.remote_addr);
564 // The rail's down-counter and the peer row's last_activity read
565 // these; nothing updated them before, so every relay connection
566 // showed 0/0 however much traffic crossed it.
567 self.app_state
568 .update_connection_stats(
569 self.server_id,
570 self.connection_id,
571 Some(n as u64),
572 None,
573 Some(1),
574 None,
575 )
576 .await;
577 inbuf.extend_from_slice(&read_buf[..n]);
578 }
579 Err(e) => {
580 error!("Failed to read Tor cell from {}: {}", self.remote_addr, e);
581 return Err(e.into());
582 }
583 }
584 }
585
586 // Send outgoing cells from forwarder tasks
587 Some(cell) = self.outgoing_rx.recv() => {
588 trace!("Sending outgoing cell ({} bytes)", cell.len());
589 self.write_counted(&cell).await?;
590 }
591 }
592 }
593 }
594
595 /// Write one cell to the peer and count it.
596 ///
597 /// The guard is dropped before the stats update, so nothing awaits `AppState` while
598 /// holding the write half - the peer-command task needs the same lock to inject a cell or
599 /// a disconnect. A cell must reach the wire whole: a short write desynchronises the peer's
600 /// framing for the rest of the connection, which is why the lock is taken around
601 /// `write_all` + `flush` together rather than per call.
602 async fn write_counted(&self, data: &[u8]) -> Result<()> {
603 {
604 let mut writer = self.write_half.lock().await;
605 writer.write_all(data).await?;
606 writer.flush().await?;
607 }
608 self.app_state
609 .update_connection_stats(
610 self.server_id,
611 self.connection_id,
612 None,
613 Some(data.len() as u64),
614 None,
615 Some(1),
616 )
617 .await;
618 Ok(())
619 }
620
621 /// Handle a variable-length cell.
622 ///
623 /// VERSIONS is answered so that a peer's first cell no longer goes unacknowledged. The
624 /// rest of the link handshake (CERTS, AUTH_CHALLENGE, NETINFO) is still not implemented,
625 /// so a real Tor client gets this far and then gives up - which is a great deal better
626 /// than the reader blocking forever, but is still not interoperability.
627 fn handle_variable_cell(&self, command: u8, payload: &[u8]) -> Option<Vec<u8>> {
628 match command {
629 CELL_COMMAND_VERSIONS => {
630 let offered: Vec<u16> = payload
631 .chunks_exact(2)
632 .map(|c| u16::from_be_bytes([c[0], c[1]]))
633 .collect();
634 Log::new(Some(&self.status_tx))
635 .info(format!("Received VERSIONS cell offering {:?}", offered));
636
637 if !offered.contains(&LINK_PROTOCOL_VERSION) {
638 warn!(
639 "Peer offered link versions {:?}, none of which is {}",
640 offered, LINK_PROTOCOL_VERSION
641 );
642 }
643
644 // VERSIONS always uses a 2-byte circuit id (tor-spec 3).
645 let mut cell = Vec::with_capacity(7);
646 cell.extend_from_slice(&0u16.to_be_bytes());
647 cell.push(CELL_COMMAND_VERSIONS);
648 cell.extend_from_slice(&2u16.to_be_bytes());
649 cell.extend_from_slice(&LINK_PROTOCOL_VERSION.to_be_bytes());
650 Some(cell)
651 }
652 _ => {
653 warn!(
654 "Ignoring variable-length cell command {} ({} byte payload): the link \
655 handshake past VERSIONS is not implemented",
656 command,
657 payload.len()
658 );
659 None
660 }
661 }
662 }
663
664 /// Handle individual cell based on type
665 async fn handle_cell(
666 &mut self,
667 cell_info: TorCellInfo,
668 cell_data: &[u8],
669 ) -> Result<Option<Vec<u8>>> {
670 match cell_info.cell_type.as_str() {
671 "CREATE2" => self.handle_create2(cell_info.circuit_id, cell_data).await,
672 "RELAY" | "RELAY_EARLY" => self.handle_relay(cell_info.circuit_id, cell_data).await,
673 "DESTROY" => {
674 debug!(
675 "Received DESTROY for circuit {}",
676 cell_info.circuit_id.as_u32()
677 );
678 self.circuit_manager
679 .destroy_circuit(cell_info.circuit_id)
680 .await;
681 Ok(None)
682 }
683 "PADDING" => {
684 trace!("Received PADDING cell");
685 Ok(None)
686 }
687 _ => {
688 warn!("Unhandled cell type: {}", cell_info.cell_type);
689 Ok(None)
690 }
691 }
692 }
693
694 /// Handle CREATE2 cell
695 async fn handle_create2(
696 &mut self,
697 circuit_id: CircuitId,
698 cell_data: &[u8],
699 ) -> Result<Option<Vec<u8>>> {
700 debug!("Processing CREATE2 for circuit {}", circuit_id.as_u32());
701
702 // Parse CREATE2 cell:
703 // CircID (4) | Command (1) | HTYPE (2) | HLEN (2) | HDATA (HLEN)
704 if cell_data.len() < 9 {
705 return Err(anyhow::anyhow!("CREATE2 cell too short"));
706 }
707
708 let htype = u16::from_be_bytes([cell_data[5], cell_data[6]]);
709 let hlen = u16::from_be_bytes([cell_data[7], cell_data[8]]);
710
711 if htype != 0x0002 {
712 // ntor
713 return Err(anyhow::anyhow!("Unsupported handshake type: {}", htype));
714 }
715
716 if hlen != 84 {
717 // ntor client handshake is 84 bytes (ID:20 + B:32 + X:32)
718 return Err(anyhow::anyhow!("Invalid ntor handshake length: {}", hlen));
719 }
720
721 // Extract client's X (last 32 bytes of handshake data)
722 let handshake_start = 9;
723 let handshake_end = handshake_start + hlen as usize;
724 if cell_data.len() < handshake_end {
725 return Err(anyhow::anyhow!("Handshake data incomplete"));
726 }
727
728 let handshake_data = &cell_data[handshake_start..handshake_end];
729 // ntor handshake: ID(20) + B(32) + X(32)
730 let client_x: [u8; 32] = handshake_data[52..84].try_into()?;
731
732 // Perform ntor handshake via circuit manager
733 let (y, auth) = self
734 .circuit_manager
735 .handle_create2(circuit_id, client_x)
736 .await?;
737
738 Log::new(Some(&self.status_tx)).info(format!(
739 "Circuit {} created successfully",
740 circuit_id.as_u32()
741 ));
742
743 // Log relay statistics (summary: file-only)
744 let stats = self.circuit_manager.get_relay_stats().await;
745 Log::new(Some(&self.status_tx)).debug(format!(
746 "Relay stats: {} circuits, {} streams, sent={} received={}",
747 stats.total_circuits,
748 stats.total_streams,
749 stats.total_bytes_sent,
750 stats.total_bytes_received
751 ));
752
753 // Send event to LLM
754 let event = Event::new(
755 &TOR_RELAY_CIRCUIT_CREATED_EVENT,
756 serde_json::json!({
757 "circuit_id": format!("0x{:08x}", circuit_id.as_u32()),
758 "client_ip": self.remote_addr.ip().to_string(),
759 }),
760 );
761
762 // Act on what the LLM decides. Discarding this result was why the circuit-created
763 // event had no effect at all.
764 match call_llm(
765 &self.llm_client,
766 &self.app_state,
767 self.server_id,
768 Some(self.connection_id),
769 &event,
770 self.protocol.as_ref(),
771 )
772 .await
773 {
774 Ok(execution_result) => {
775 for message in execution_result.messages {
776 let _ = self.status_tx.send(message);
777 }
778 for protocol_result in execution_result.protocol_results {
779 match protocol_result {
780 // A DESTROY chosen here replaces the CREATED2 we were about to send.
781 ActionResult::Output(data) => {
782 info!(
783 "Tor relay circuit 0x{:08x}: CREATE2 answered by the model's \
784 own cell decision=model_answer",
785 circuit_id.as_u32()
786 );
787 return Ok(Some(data));
788 }
789 ActionResult::CloseConnection => {
790 info!(
791 "Tor relay circuit 0x{:08x}: model closed the connection on \
792 CREATE2 decision=model_reject",
793 circuit_id.as_u32()
794 );
795 self.circuit_manager.destroy_circuit(circuit_id).await;
796 return Err(anyhow::anyhow!("LLM requested close"));
797 }
798 _ => {}
799 }
800 }
801 }
802 Err(e) => {
803 // CREATED2 is admission: it completes the ntor handshake and hands the peer a
804 // circuit it will build on. The model was asked whether to admit this one and
805 // gave no answer, so admitting it anyway would turn a backend outage into
806 // "every circuit accepted". Tear it down instead, in Tor's own vocabulary:
807 // DESTROY (tor-spec 5.4) with reason 2 INTERNAL — the fault is the relay's,
808 // not the client's and not a protocol violation. The circuit's crypto state
809 // is dropped too, so what this relay believes and what the peer was told agree.
810 //
811 // Non-fatal to the connection: a DESTROY is the wire answer, so WARN.
812 Log::new(Some(&self.status_tx)).warn(format!(
813 "Tor relay circuit 0x{:08x}: LLM call failed on CREATE2 ({}) - sent DESTROY \
814 (reason 2 INTERNAL) decision=fail_closed_llm_error",
815 circuit_id.as_u32(),
816 e
817 ));
818 self.circuit_manager.destroy_circuit(circuit_id).await;
819 return Ok(Some(self.create_destroy_cell_with_reason(
820 circuit_id,
821 DESTROY_REASON_INTERNAL,
822 )));
823 }
824 }
825
826 info!(
827 "Tor relay circuit 0x{:08x}: CREATED2 sent decision=model_answer",
828 circuit_id.as_u32()
829 );
830
831 // Build CREATED2 response
832 // CircID (4) | Command (1) | HLEN (2) | HDATA (HLEN)
833 let mut response = Vec::with_capacity(71);
834 response.extend_from_slice(&circuit_id.to_bytes());
835 response.push(11); // CREATED2 command (tor-spec 3; 10 is CREATE2)
836 response.extend_from_slice(&64u16.to_be_bytes()); // HLEN = 64 (Y:32 + AUTH:32)
837 response.extend_from_slice(&y);
838 response.extend_from_slice(&auth);
839
840 // Pad to 514 bytes
841 response.resize(514, 0);
842
843 Ok(Some(response))
844 }
845
846 /// Handle RELAY cell
847 async fn handle_relay(
848 &mut self,
849 circuit_id: CircuitId,
850 cell_data: &[u8],
851 ) -> Result<Option<Vec<u8>>> {
852 trace!("Processing RELAY cell for circuit {}", circuit_id.as_u32());
853
854 // Extract relay payload (skip CircID:4 + Command:1)
855 let mut relay_payload = cell_data[5..514].to_vec();
856
857 // Decrypt relay cell
858 self.circuit_manager
859 .decrypt_relay_cell(circuit_id, &mut relay_payload)
860 .await?;
861
862 // Track bytes received from client (entire cell)
863 let _ = self.circuit_manager.record_received(circuit_id, 509).await;
864
865 // Track RELAY cell for circuit-level flow control - send SENDME if needed
866 let send_circuit_sendme = self
867 .circuit_manager
868 .record_relay_received(circuit_id)
869 .await
870 .unwrap_or(false);
871 if send_circuit_sendme {
872 debug!(
873 "Sending circuit-level SENDME for circuit {}",
874 circuit_id.as_u32()
875 );
876 let sendme_cell = build_relay_cell(
877 circuit_id.as_u32(),
878 0, // Stream ID 0 for circuit-level SENDME
879 relay_command::SENDME,
880 &[],
881 );
882 let mut encrypted = sendme_cell.clone();
883 self.circuit_manager
884 .encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
885 .await?;
886 let _ = self.outgoing_tx.send(encrypted);
887 }
888
889 // Parse relay cell header:
890 // Command (1) | Recognized (2) | StreamID (2) | Digest (4) | Length (2) | Data (Length)
891 if relay_payload.len() < 11 {
892 return Err(anyhow::anyhow!("RELAY cell too short"));
893 }
894
895 let relay_cmd = relay_payload[0];
896 let recognized = u16::from_be_bytes([relay_payload[1], relay_payload[2]]);
897 let stream_id_u16 = u16::from_be_bytes([relay_payload[3], relay_payload[4]]);
898 let stream_id = StreamId::new(stream_id_u16);
899 let length = u16::from_be_bytes([relay_payload[9], relay_payload[10]]) as usize;
900
901 // `length` comes from the decrypted payload, so it is whatever the peer's ciphertext
902 // decrypts to - up to 65535 - while the payload is only 509 bytes. Slicing on it
903 // unchecked panicked the connection task on essentially any malformed or
904 // wrongly-keyed cell.
905 if 11 + length > relay_payload.len() {
906 warn!(
907 "RELAY cell on circuit {} declares {} bytes of data but only {} remain; dropping",
908 circuit_id.as_u32(),
909 length,
910 relay_payload.len().saturating_sub(11)
911 );
912 return Ok(None);
913 }
914 let data = &relay_payload[11..11 + length];
915
916 // Check if this cell is for us (recognized should be 0)
917 if recognized != 0 {
918 // Not for us, would normally forward but we're endpoint
919 trace!("RELAY cell not recognized, dropping");
920 return Ok(None);
921 }
922
923 let relay_cmd_name = match relay_cmd {
924 relay_command::BEGIN => "BEGIN",
925 relay_command::DATA => "DATA",
926 relay_command::END => "END",
927 relay_command::CONNECTED => "CONNECTED",
928 relay_command::SENDME => "SENDME",
929 relay_command::EXTEND => "EXTEND",
930 relay_command::EXTENDED => "EXTENDED",
931 relay_command::TRUNCATE => "TRUNCATE",
932 relay_command::TRUNCATED => "TRUNCATED",
933 relay_command::DROP => "DROP",
934 relay_command::RESOLVE => "RESOLVE",
935 relay_command::RESOLVED => "RESOLVED",
936 relay_command::BEGIN_DIR => "BEGIN_DIR",
937 _ => "UNKNOWN",
938 };
939
940 debug!(
941 "RELAY cell: command={} ({}), stream={}, length={}",
942 relay_cmd,
943 relay_cmd_name,
944 stream_id.as_u16(),
945 length
946 );
947
948 // Handle specific relay commands
949 match relay_cmd {
950 relay_command::BEGIN => {
951 return self.handle_begin_cell(circuit_id, stream_id, data).await;
952 }
953 relay_command::BEGIN_DIR => {
954 return self.handle_begin_dir_cell(circuit_id, stream_id).await;
955 }
956 relay_command::DATA => {
957 return self.handle_data_cell(circuit_id, stream_id, data).await;
958 }
959 relay_command::END => {
960 return self.handle_end_cell(circuit_id, stream_id, data).await;
961 }
962 relay_command::SENDME => {
963 return self.handle_sendme_cell(circuit_id, stream_id).await;
964 }
965 _ => {
966 // For other commands, send event to LLM
967 let event = Event::new(
968 &TOR_RELAY_RELAY_CELL_EVENT,
969 serde_json::json!({
970 "circuit_id": format!("0x{:08x}", circuit_id.as_u32()),
971 "relay_command": relay_cmd_name,
972 "stream_id": stream_id.as_u16(),
973 "length": length,
974 "client_ip": self.remote_addr.ip().to_string(),
975 }),
976 );
977
978 // Get LLM response for how to handle this
979 match call_llm(
980 &self.llm_client,
981 &self.app_state,
982 self.server_id,
983 Some(self.connection_id),
984 &event,
985 self.protocol.as_ref(),
986 )
987 .await
988 {
989 Ok(execution_result) => {
990 for message in execution_result.messages {
991 let _ = self.status_tx.send(message);
992 }
993 // Execute protocol actions
994 for protocol_result in execution_result.protocol_results {
995 match protocol_result {
996 ActionResult::Output(data) => {
997 debug!(
998 "Tor relay circuit 0x{:08x}: RELAY {} answered \
999 decision=model_answer",
1000 circuit_id.as_u32(),
1001 relay_cmd_name
1002 );
1003 return Ok(Some(data));
1004 }
1005 ActionResult::CloseConnection => {
1006 info!(
1007 "Tor relay circuit 0x{:08x}: model closed the \
1008 connection on RELAY {} decision=model_reject",
1009 circuit_id.as_u32(),
1010 relay_cmd_name
1011 );
1012 return Err(anyhow::anyhow!("LLM requested close"));
1013 }
1014 _ => {}
1015 }
1016 }
1017 debug!(
1018 "Tor relay circuit 0x{:08x}: RELAY {} left unanswered by the model \
1019 decision=model_silent",
1020 circuit_id.as_u32(),
1021 relay_cmd_name
1022 );
1023 }
1024 Err(e) => {
1025 // This branch is reached only for RELAY commands the relay does not
1026 // implement itself (EXTEND, TRUNCATE, RESOLVE, DROP, ...), so the
1027 // model was the entire answer. Swallowing the error left the peer
1028 // waiting on a circuit that would never say anything again.
1029 //
1030 // Tor's own vocabulary for "this relay cannot carry on with your
1031 // circuit" is DESTROY (tor-spec 5.4), with reason 2 INTERNAL: an
1032 // error at the relay, not at the client and not a protocol violation.
1033 // It needs no relay-cell encryption, so it is still deliverable when
1034 // the circuit crypto is the thing that is unhappy, and it is the one
1035 // answer that cannot be mistaken for the EXTENDED/RESOLVED/TRUNCATED
1036 // the peer asked for. There is no "try again" cell here to express an
1037 // overload with, so overload and outage are answered identically and
1038 // distinguished only in the log.
1039 // Non-fatal: a DESTROY cell (reason 2 INTERNAL) is the wire answer, so WARN.
1040 Log::new(Some(&self.status_tx)).warn(format!(
1041 "Tor relay circuit 0x{:08x}: LLM call failed on RELAY {} ({}) - sent \
1042 DESTROY (reason 2 INTERNAL) decision=fail_closed_llm_error",
1043 circuit_id.as_u32(),
1044 relay_cmd_name,
1045 e
1046 ));
1047 if crate::llm::is_overload_error(&e) {
1048 warn!(
1049 "Tor relay circuit {} destroyed: LLM capacity exhausted",
1050 circuit_id.as_u32()
1051 );
1052 }
1053 // Drop the circuit's own state too, so the DESTROY we send and what
1054 // this relay believes are the same thing.
1055 self.circuit_manager.destroy_circuit(circuit_id).await;
1056 return Ok(Some(self.create_destroy_cell_with_reason(
1057 circuit_id,
1058 DESTROY_REASON_INTERNAL,
1059 )));
1060 }
1061 }
1062 }
1063 }
1064
1065 // Default: no response
1066 Ok(None)
1067 }
1068
1069 /// Handle BEGIN cell - establish TCP connection to target
1070 async fn handle_begin_cell(
1071 &mut self,
1072 circuit_id: CircuitId,
1073 stream_id: StreamId,
1074 data: &[u8],
1075 ) -> Result<Option<Vec<u8>>> {
1076 // Parse target address
1077 let target = parse_begin_target(data)?;
1078
1079 Log::new(Some(&self.status_tx)).info(format!(
1080 "BEGIN stream {} on circuit {} to {}",
1081 stream_id.as_u16(),
1082 circuit_id.as_u32(),
1083 target
1084 ));
1085
1086 // Create stream in circuit manager.
1087 //
1088 // A refusal here is a per-stream condition, not a per-circuit one: the peer has hit
1089 // MAX_STREAMS_PER_CIRCUIT, or reused a live stream id. Answer END/RESOURCE_LIMIT and
1090 // keep the circuit up. Propagating with `?` would take the whole connection down
1091 // through the DESTROY path in `handle()`, which hands a peer that opens too many
1092 // streams a way to kill everything else riding the same circuit.
1093 if let Err(e) = self
1094 .circuit_manager
1095 .create_stream(circuit_id, stream_id, target.clone())
1096 .await
1097 {
1098 Log::new(Some(&self.status_tx)).warn(format!(
1099 "Refusing BEGIN stream {} on circuit {}: {}",
1100 stream_id.as_u16(),
1101 circuit_id.as_u32(),
1102 e
1103 ));
1104 let end_cell = build_relay_cell(
1105 circuit_id.as_u32(),
1106 stream_id.as_u16(),
1107 relay_command::END,
1108 &[end_reason::RESOURCE_LIMIT],
1109 );
1110 let mut encrypted = end_cell.clone();
1111 self.circuit_manager
1112 .encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
1113 .await?;
1114 let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
1115 return Ok(Some(encrypted));
1116 }
1117
1118 // Attempt to connect to target
1119 match connect_to_target(&target).await {
1120 Ok(tcp_stream) => {
1121 Log::new(Some(&self.status_tx)).info(format!(
1122 "Connected to {} for stream {}",
1123 target,
1124 stream_id.as_u16()
1125 ));
1126
1127 // Store TCP connection in stream
1128 self.circuit_manager
1129 .set_stream_active(circuit_id, stream_id, tcp_stream)
1130 .await?;
1131
1132 // Build CONNECTED response
1133 let connected_cell = build_relay_cell(
1134 circuit_id.as_u32(),
1135 stream_id.as_u16(),
1136 relay_command::CONNECTED,
1137 &[],
1138 );
1139
1140 // Encrypt response
1141 let mut encrypted = connected_cell.clone();
1142 self.circuit_manager
1143 .encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
1144 .await?;
1145
1146 // Track bytes sent to client
1147 let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
1148
1149 // Start forwarding task for this stream
1150 self.spawn_stream_forwarder(circuit_id, stream_id, self.outgoing_tx.clone())
1151 .await?;
1152
1153 Ok(Some(encrypted))
1154 }
1155 Err(e) => {
1156 // Non-fatal: an END cell (CONNECT_REFUSED) is sent to the client, so WARN.
1157 Log::new(Some(&self.status_tx))
1158 .warn(format!("Failed to connect to {}: {}", target, e));
1159
1160 // Close stream
1161 let _ = self
1162 .circuit_manager
1163 .close_stream(circuit_id, stream_id)
1164 .await;
1165
1166 // Build END response with error reason
1167 let end_cell = build_relay_cell(
1168 circuit_id.as_u32(),
1169 stream_id.as_u16(),
1170 relay_command::END,
1171 &[end_reason::CONNECT_REFUSED],
1172 );
1173
1174 // Encrypt response
1175 let mut encrypted = end_cell.clone();
1176 self.circuit_manager
1177 .encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
1178 .await?;
1179
1180 // Track bytes sent to client
1181 let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
1182
1183 Ok(Some(encrypted))
1184 }
1185 }
1186 }
1187
1188 /// Handle BEGIN_DIR cell - create directory stream for serving consensus
1189 async fn handle_begin_dir_cell(
1190 &mut self,
1191 circuit_id: CircuitId,
1192 stream_id: StreamId,
1193 ) -> Result<Option<Vec<u8>>> {
1194 Log::new(Some(&self.status_tx)).info(format!(
1195 "BEGIN_DIR stream {} on circuit {} (directory request over circuit)",
1196 stream_id.as_u16(),
1197 circuit_id.as_u32()
1198 ));
1199
1200 // Create directory stream in circuit manager
1201 self.circuit_manager
1202 .create_directory_stream(circuit_id, stream_id)
1203 .await?;
1204
1205 info!(
1206 "Directory stream {} ready on circuit {}",
1207 stream_id.as_u16(),
1208 circuit_id.as_u32()
1209 );
1210
1211 // Build CONNECTED response (same as normal stream)
1212 let connected_cell = build_relay_cell(
1213 circuit_id.as_u32(),
1214 stream_id.as_u16(),
1215 relay_command::CONNECTED,
1216 &[],
1217 );
1218
1219 // Encrypt response
1220 let mut encrypted = connected_cell.clone();
1221 self.circuit_manager
1222 .encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
1223 .await?;
1224
1225 Ok(Some(encrypted))
1226 }
1227
1228 /// Handle DATA cell - forward data to TCP connection or handle directory request
1229 async fn handle_data_cell(
1230 &mut self,
1231 circuit_id: CircuitId,
1232 stream_id: StreamId,
1233 data: &[u8],
1234 ) -> Result<Option<Vec<u8>>> {
1235 trace!(
1236 "DATA cell for stream {} ({} bytes)",
1237 stream_id.as_u16(),
1238 data.len()
1239 );
1240
1241 // Track DATA cell for stream-level flow control - send SENDME if needed
1242 let send_stream_sendme = self
1243 .circuit_manager
1244 .record_stream_data_received(circuit_id, stream_id)
1245 .await
1246 .unwrap_or(false);
1247
1248 if send_stream_sendme {
1249 debug!(
1250 "Sending stream-level SENDME for stream {}",
1251 stream_id.as_u16()
1252 );
1253 let sendme_cell = build_relay_cell(
1254 circuit_id.as_u32(),
1255 stream_id.as_u16(),
1256 relay_command::SENDME,
1257 &[],
1258 );
1259 let mut encrypted = sendme_cell.clone();
1260 self.circuit_manager
1261 .encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
1262 .await?;
1263 let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
1264 let _ = self.outgoing_tx.send(encrypted);
1265 }
1266
1267 // Check if this is a directory stream (BEGIN_DIR)
1268 if self
1269 .circuit_manager
1270 .is_directory_stream(circuit_id, stream_id)
1271 .await?
1272 {
1273 return self
1274 .handle_directory_data(circuit_id, stream_id, data)
1275 .await;
1276 }
1277
1278 // Get the write half of this stream's TCP connection
1279 if let Some(connection) = self
1280 .circuit_manager
1281 .get_stream_writer(circuit_id, stream_id)
1282 .await?
1283 {
1284 // Write data to TCP connection
1285 let mut conn = connection.lock().await;
1286 if let Err(e) = conn.write_all(data).await {
1287 error!("Failed to write to stream {}: {}", stream_id.as_u16(), e);
1288
1289 // Close stream
1290 drop(conn); // Release lock before closing
1291 let _ = self
1292 .circuit_manager
1293 .close_stream(circuit_id, stream_id)
1294 .await;
1295
1296 // Send END cell
1297 let end_cell = build_relay_cell(
1298 circuit_id.as_u32(),
1299 stream_id.as_u16(),
1300 relay_command::END,
1301 &[end_reason::MISC],
1302 );
1303
1304 let mut encrypted = end_cell.clone();
1305 self.circuit_manager
1306 .encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
1307 .await?;
1308
1309 // Track bytes sent to client
1310 let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
1311
1312 return Ok(Some(encrypted));
1313 }
1314
1315 trace!(
1316 "Forwarded {} bytes to stream {}",
1317 data.len(),
1318 stream_id.as_u16()
1319 );
1320 } else {
1321 warn!("Stream {} not found or not active", stream_id.as_u16());
1322 }
1323
1324 Ok(None)
1325 }
1326
1327 /// Handle directory stream DATA - accumulate HTTP request and respond
1328 async fn handle_directory_data(
1329 &mut self,
1330 circuit_id: CircuitId,
1331 stream_id: StreamId,
1332 data: &[u8],
1333 ) -> Result<Option<Vec<u8>>> {
1334 // Append data to directory request buffer
1335 self.circuit_manager
1336 .append_directory_data(circuit_id, stream_id, data)
1337 .await?;
1338
1339 // Get accumulated request
1340 let request_data = self
1341 .circuit_manager
1342 .get_directory_request(circuit_id, stream_id)
1343 .await?;
1344
1345 // Check if we have a complete HTTP request (ends with \r\n\r\n)
1346 if let Some(pos) = request_data.windows(4).position(|w| w == b"\r\n\r\n") {
1347 // Extract HTTP request line
1348 let request_str = String::from_utf8_lossy(&request_data[..pos]);
1349 let first_line = request_str.lines().next().unwrap_or("");
1350
1351 debug!("Directory request: {}", first_line);
1352
1353 // Parse HTTP method and path
1354 let parts: Vec<&str> = first_line.split_whitespace().collect();
1355 let (_method, path) = if parts.len() >= 2 {
1356 (parts[0], parts[1])
1357 } else {
1358 ("GET", "/")
1359 };
1360
1361 // Generate consensus response
1362 // TODO: Call LLM to generate dynamic consensus based on path and instruction
1363 let consensus = if path.contains("consensus") {
1364 Self::generate_test_consensus()
1365 } else {
1366 b"HTTP/1.0 404 Not Found\r\n\r\n".to_vec()
1367 };
1368
1369 info!(
1370 "Serving {} response ({} bytes) for directory path: {}",
1371 if path.contains("consensus") {
1372 "consensus"
1373 } else {
1374 "404"
1375 },
1376 consensus.len(),
1377 path
1378 );
1379
1380 // Send response as DATA cells
1381 self.send_directory_response(circuit_id, stream_id, &consensus)
1382 .await?;
1383
1384 // Close stream after response
1385 let end_cell = build_relay_cell(
1386 circuit_id.as_u32(),
1387 stream_id.as_u16(),
1388 relay_command::END,
1389 &[end_reason::DONE],
1390 );
1391
1392 let mut encrypted = end_cell.clone();
1393 self.circuit_manager
1394 .encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
1395 .await?;
1396
1397 return Ok(Some(encrypted));
1398 }
1399
1400 Ok(None)
1401 }
1402
1403 /// Send directory response data as multiple DATA cells
1404 async fn send_directory_response(
1405 &mut self,
1406 circuit_id: CircuitId,
1407 stream_id: StreamId,
1408 data: &[u8],
1409 ) -> Result<()> {
1410 const MAX_RELAY_PAYLOAD: usize = 498; // tor-spec: relay cell payload max
1411
1412 for chunk in data.chunks(MAX_RELAY_PAYLOAD) {
1413 let data_cell = build_relay_cell(
1414 circuit_id.as_u32(),
1415 stream_id.as_u16(),
1416 relay_command::DATA,
1417 chunk,
1418 );
1419
1420 let mut encrypted = data_cell.clone();
1421 self.circuit_manager
1422 .encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
1423 .await?;
1424
1425 self.outgoing_tx.send(encrypted)?;
1426 let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
1427 }
1428
1429 Ok(())
1430 }
1431
1432 /// Handle END cell - close stream
1433 async fn handle_end_cell(
1434 &mut self,
1435 circuit_id: CircuitId,
1436 stream_id: StreamId,
1437 data: &[u8],
1438 ) -> Result<Option<Vec<u8>>> {
1439 let reason = if data.is_empty() {
1440 end_reason::DONE
1441 } else {
1442 data[0]
1443 };
1444
1445 Log::new(Some(&self.status_tx)).debug(format!(
1446 "END stream {} (reason: {})",
1447 stream_id.as_u16(),
1448 reason
1449 ));
1450
1451 // Close stream
1452 let _ = self
1453 .circuit_manager
1454 .close_stream(circuit_id, stream_id)
1455 .await;
1456
1457 Ok(None)
1458 }
1459
1460 /// Handle SENDME cell - update flow control windows
1461 async fn handle_sendme_cell(
1462 &mut self,
1463 circuit_id: CircuitId,
1464 stream_id: StreamId,
1465 ) -> Result<Option<Vec<u8>>> {
1466 if stream_id.as_u16() == 0 {
1467 // Circuit-level SENDME
1468 debug!(
1469 "Received circuit-level SENDME for circuit {}",
1470 circuit_id.as_u32()
1471 );
1472 let _ = self
1473 .circuit_manager
1474 .process_circuit_sendme(circuit_id)
1475 .await;
1476 } else {
1477 // Stream-level SENDME
1478 debug!(
1479 "Received stream-level SENDME for stream {}",
1480 stream_id.as_u16()
1481 );
1482 let _ = self
1483 .circuit_manager
1484 .process_stream_sendme(circuit_id, stream_id)
1485 .await;
1486 }
1487 Ok(None)
1488 }
1489
1490 /// Generate a test consensus document for Arti bootstrap
1491 /// Returns HTTP response with minimal but valid consensus
1492 fn generate_test_consensus() -> Vec<u8> {
1493 use crate::utils::clock::{SystemTime, UNIX_EPOCH};
1494
1495 // Get current time for valid-after/until
1496 let now = SystemTime::now()
1497 .duration_since(UNIX_EPOCH)
1498 .unwrap()
1499 .as_secs();
1500 let hour = now - (now % 3600); // Round down to hour
1501
1502 // Format timestamps (RFC 3339 style for Tor)
1503 let valid_after = chrono::DateTime::from_timestamp(hour as i64, 0)
1504 .unwrap()
1505 .format("%Y-%m-%d %H:%M:%S")
1506 .to_string();
1507 let fresh_until = chrono::DateTime::from_timestamp((hour + 3600) as i64, 0)
1508 .unwrap()
1509 .format("%Y-%m-%d %H:%M:%S")
1510 .to_string();
1511 let valid_until = chrono::DateTime::from_timestamp((hour + 10800) as i64, 0)
1512 .unwrap()
1513 .format("%Y-%m-%d %H:%M:%S")
1514 .to_string();
1515
1516 let consensus_body = format!(
1517 "network-status-version 3\n\
1518 vote-status consensus\n\
1519 consensus-method 35\n\
1520 valid-after {}\n\
1521 fresh-until {}\n\
1522 valid-until {}\n\
1523 voting-delay 300 300\n\
1524 client-versions 0.4.7.0-alpha-dev,0.4.8.0\n\
1525 server-versions 0.4.7.0-alpha-dev,0.4.8.0\n\
1526 known-flags Authority BadExit Exit Fast Guard HSDir NoEdConsensus Running Stable StaleDesc Sybil V2Dir Valid\n\
1527 params CircuitPriorityHalflifeMsec=30000 DoSCircuitCreationEnabled=1 DoSConnectionEnabled=1\n\
1528 r TestRelay1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 127.0.0.1 9001 127.0.0.1 9030\n\
1529 s Exit Fast Guard HSDir Running Stable V2Dir Valid\n\
1530 v Tor 0.4.8.0\n\
1531 w Bandwidth=5000\n\
1532 p accept 1-65535\n\
1533 r TestRelay2 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= 127.0.0.2 9001 127.0.0.2 9030\n\
1534 s Exit Fast Guard HSDir Running Stable V2Dir Valid\n\
1535 v Tor 0.4.8.0\n\
1536 w Bandwidth=5000\n\
1537 p accept 1-65535\n\
1538 r TestRelay3 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC= 127.0.0.3 9001 127.0.0.3 9030\n\
1539 s Exit Fast Guard HSDir Running Stable V2Dir Valid\n\
1540 v Tor 0.4.8.0\n\
1541 w Bandwidth=5000\n\
1542 p accept 1-65535\n\
1543 r TestRelay4 DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD= 127.0.0.4 9001 127.0.0.4 9030\n\
1544 s Exit Fast Guard HSDir Running Stable V2Dir Valid\n\
1545 v Tor 0.4.8.0\n\
1546 w Bandwidth=5000\n\
1547 p accept 1-65535\n\
1548 directory-footer\n\
1549 bandwidth-weights Wbd=0 Wbe=0 Wbg=4096 Wbm=10000 Wdb=10000 Web=10000 Wed=10000 Wee=10000 Weg=10000 Wem=10000 Wgb=10000 Wgd=0 Wgg=5920 Wgm=5920 Wmb=10000 Wmd=0 Wme=0 Wmg=4096 Wmm=10000\n\
1550 directory-signature 0000000000000000000000000000000000000000 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n\
1551 -----BEGIN SIGNATURE-----\n\
1552 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n\
1553 -----END SIGNATURE-----\n",
1554 valid_after, fresh_until, valid_until
1555 );
1556
1557 // Build HTTP response
1558 let http_response = format!(
1559 "HTTP/1.0 200 OK\r\n\
1560 Content-Type: text/plain\r\n\
1561 Content-Length: {}\r\n\
1562 \r\n\
1563 {}",
1564 consensus_body.len(),
1565 consensus_body
1566 );
1567
1568 http_response.into_bytes()
1569 }
1570
1571 /// Spawn background task to forward data from TCP connection back to Tor client
1572 async fn spawn_stream_forwarder(
1573 &self,
1574 circuit_id: CircuitId,
1575 stream_id: StreamId,
1576 outgoing_tx: mpsc::UnboundedSender<Vec<u8>>,
1577 ) -> Result<()> {
1578 // Read half only. Holding a lock over the whole TcpStream here parked the
1579 // client-to-target write path forever, because this task waits in `read()`.
1580 let connection = self
1581 .circuit_manager
1582 .get_stream_reader(circuit_id, stream_id)
1583 .await?
1584 .ok_or_else(|| anyhow::anyhow!("Stream not found"))?;
1585
1586 let circuit_mgr = self.circuit_manager.clone();
1587 let status_tx = self.status_tx.clone();
1588
1589 // Tracked, not detached: stop_server must abort this task too.
1590 let task_owner = self.app_state.clone();
1591 task_owner
1592 .spawn_server_task(self.server_id, async move {
1593 let mut buffer = vec![0u8; 498]; // Max relay cell data size
1594
1595 loop {
1596 let bytes_read = {
1597 let mut conn = connection.lock().await;
1598 match conn.read(&mut buffer).await {
1599 Ok(0) => {
1600 // EOF - connection closed
1601 debug!("Stream {} EOF from target", stream_id.as_u16());
1602 break;
1603 }
1604 Ok(n) => n,
1605 Err(e) => {
1606 error!("Failed to read from stream {}: {}", stream_id.as_u16(), e);
1607 break;
1608 }
1609 }
1610 };
1611
1612 // Build DATA relay cell
1613 let mut data_cell = build_relay_cell(
1614 circuit_id.as_u32(),
1615 stream_id.as_u16(),
1616 relay_command::DATA,
1617 &buffer[..bytes_read],
1618 );
1619
1620 // Encrypt relay cell payload
1621 if let Err(e) = circuit_mgr
1622 .encrypt_relay_cell(circuit_id, &mut data_cell[5..514])
1623 .await
1624 {
1625 error!(
1626 "Failed to encrypt DATA cell for stream {}: {}",
1627 stream_id.as_u16(),
1628 e
1629 );
1630 break;
1631 }
1632
1633 // Track bytes sent to client
1634 let _ = circuit_mgr.record_sent(circuit_id, 509).await;
1635
1636 // Send encrypted cell through channel
1637 if let Err(e) = outgoing_tx.send(data_cell) {
1638 error!(
1639 "Failed to send DATA cell for stream {}: {}",
1640 stream_id.as_u16(),
1641 e
1642 );
1643 break;
1644 }
1645
1646 trace!(
1647 "Forwarded {} bytes from stream {} back to client",
1648 bytes_read,
1649 stream_id.as_u16()
1650 );
1651 }
1652
1653 // Send END cell when stream closes
1654 let mut end_cell = build_relay_cell(
1655 circuit_id.as_u32(),
1656 stream_id.as_u16(),
1657 relay_command::END,
1658 &[end_reason::DONE],
1659 );
1660
1661 // Encrypt END cell
1662 if let Ok(_) = circuit_mgr
1663 .encrypt_relay_cell(circuit_id, &mut end_cell[5..514])
1664 .await
1665 {
1666 let _ = circuit_mgr.record_sent(circuit_id, 509).await;
1667 let _ = outgoing_tx.send(end_cell);
1668 }
1669
1670 // Close stream
1671 let _ = circuit_mgr.close_stream(circuit_id, stream_id).await;
1672 Log::new(Some(&status_tx)).debug(format!("Stream {} closed", stream_id.as_u16()));
1673 })
1674 .await;
1675
1676 Ok(())
1677 }
1678
1679 /// Create a DESTROY cell (tor-spec 5.4) carrying `reason`.
1680 fn create_destroy_cell_with_reason(&self, circuit_id: CircuitId, reason: u8) -> Vec<u8> {
1681 let mut cell = Vec::with_capacity(FIXED_CELL_LEN);
1682 cell.extend_from_slice(&circuit_id.to_bytes());
1683 cell.push(4); // DESTROY command
1684 cell.push(reason);
1685 cell.resize(FIXED_CELL_LEN, 0); // Pad to 514 bytes
1686 cell
1687 }
1688
1689 /// Create DESTROY cell for a malformed or unprocessable cell.
1690 fn create_destroy_cell(&self, circuit_id: CircuitId) -> Vec<u8> {
1691 self.create_destroy_cell_with_reason(circuit_id, DESTROY_REASON_PROTOCOL)
1692 }
1693}
1694
1695/// Tor cell information
1696#[derive(Debug, Clone)]
1697struct TorCellInfo {
1698 circuit_id: CircuitId,
1699 cell_type: String,
1700}
1701
1702/// The link protocol version this relay frames cells for: 4-byte circuit ids, 514-byte
1703/// fixed cells.
1704/// How long to wait for a peer's first cell after the TLS handshake.
1705///
1706/// The Tor link protocol is client-speaks-first once TLS is up: the VERSIONS cell is the first
1707/// thing a client sends and the relay says nothing before it. A peer that has completed TLS and
1708/// then gone quiet has begun no link handshake, so it gets the short bound — and this is the
1709/// one an unauthenticated flood lives under, since it never reaches the idle bound below.
1710const FIRST_CELL_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
1711
1712/// How long to wait for a *further* cell once the link is up.
1713///
1714/// Fifteen minutes, derived from Tor's own answer to the same question. `KeepalivePeriod`
1715/// defaults to five minutes: a relay sends a PADDING cell that often on an open connection
1716/// precisely so an idle-but-live link keeps proving it is live through firewalls that would
1717/// otherwise drop it. Three of those periods with neither a cell nor a padding keepalive means
1718/// the peer is gone, not quiet. (Tor's `CircuitIdleTimeout` — an hour — is about circuits, not
1719/// about a connection that is producing nothing at all, so it is not the number to copy here.)
1720const IDLE_BETWEEN_CELLS_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(900);
1721
1722/// Concurrent inbound connections this relay admits.
1723const MAX_CONNECTIONS: usize = crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
1724
1725/// What a peer over [`MAX_CONNECTIONS`] is told before the socket closes.
1726///
1727/// The cap is applied on the raw TCP accept, before the TLS handshake, so the only vocabulary
1728/// available is TLS's: a plaintext fatal alert record — content type 21, legacy record version
1729/// 0x0303, length 2, level `fatal`(2), description `internal_error`(80). TLS has no "server
1730/// busy" alert and the Tor link protocol has nothing at all that may precede VERSIONS, so this
1731/// is as close as either layer comes. A client reports a fatal alert rather than a bare reset.
1732const CONNECTION_CAP_REFUSAL: &[u8] = &[0x15, 0x03, 0x03, 0x00, 0x02, 0x02, 0x50];
1733
1734const LINK_PROTOCOL_VERSION: u16 = 4;
1735/// tor-spec 3, command 7. Variable length, and the only cell with a 2-byte circuit id.
1736const CELL_COMMAND_VERSIONS: u8 = 7;
1737/// Fixed cell size for link protocol v4: 4 (circuit id) + 1 (command) + 509 (payload).
1738const FIXED_CELL_LEN: usize = 514;
1739
1740/// tor-spec 5.4 DESTROY reason: "a protocol error occurred".
1741const DESTROY_REASON_PROTOCOL: u8 = 1;
1742/// tor-spec 5.4 DESTROY reason: "internal error at the relay". The relay tearing a circuit
1743/// down because of a fault of its own, which is exactly what an LLM backend failure is.
1744const DESTROY_REASON_INTERNAL: u8 = 2;
1745
1746/// One cell framed off the wire.
1747#[derive(Debug)]
1748enum FramedCell {
1749 /// A fixed-length v4 cell. `raw` is all 514 bytes, so the existing handlers can keep
1750 /// indexing it directly.
1751 Fixed { circuit_id: CircuitId, raw: Vec<u8> },
1752 /// A variable-length cell: VERSIONS (7), or any command >= 128 (VPADDING, CERTS,
1753 /// AUTH_CHALLENGE, AUTHENTICATE, AUTHORIZE).
1754 Variable {
1755 #[allow(dead_code)]
1756 circuit_id: u32,
1757 command: u8,
1758 payload: Vec<u8>,
1759 },
1760}
1761
1762/// Frame one cell out of `buf`, consuming its bytes. `None` means more bytes are needed.
1763///
1764/// `expecting_versions` must be true only for the first cell of a connection. tor-spec 3
1765/// allows VERSIONS only there, and gives it a 2-byte circuit id while every other cell in
1766/// link protocol v4 has a 4-byte one — so the command byte sits at a different offset and
1767/// the two layouts are only distinguishable because of that rule.
1768fn take_cell(buf: &mut Vec<u8>, expecting_versions: bool) -> Option<FramedCell> {
1769 if expecting_versions && buf.len() >= 3 && buf[2] == CELL_COMMAND_VERSIONS {
1770 // CircID (2) | Command (1) | Length (2) | Payload (Length)
1771 if buf.len() < 5 {
1772 return None;
1773 }
1774 let length = u16::from_be_bytes([buf[3], buf[4]]) as usize;
1775 if buf.len() < 5 + length {
1776 return None;
1777 }
1778 let circuit_id = u16::from_be_bytes([buf[0], buf[1]]) as u32;
1779 let payload = buf[5..5 + length].to_vec();
1780 buf.drain(..5 + length);
1781 return Some(FramedCell::Variable {
1782 circuit_id,
1783 command: CELL_COMMAND_VERSIONS,
1784 payload,
1785 });
1786 }
1787
1788 if buf.len() < 5 {
1789 return None;
1790 }
1791 let circuit_id = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
1792 let command = buf[4];
1793
1794 // Commands 128 and above are variable-length in every link protocol version.
1795 if command >= 128 {
1796 if buf.len() < 7 {
1797 return None;
1798 }
1799 let length = u16::from_be_bytes([buf[5], buf[6]]) as usize;
1800 if buf.len() < 7 + length {
1801 return None;
1802 }
1803 let payload = buf[7..7 + length].to_vec();
1804 buf.drain(..7 + length);
1805 return Some(FramedCell::Variable {
1806 circuit_id,
1807 command,
1808 payload,
1809 });
1810 }
1811
1812 if buf.len() < FIXED_CELL_LEN {
1813 return None;
1814 }
1815 let raw = buf[..FIXED_CELL_LEN].to_vec();
1816 buf.drain(..FIXED_CELL_LEN);
1817 Some(FramedCell::Fixed {
1818 circuit_id: CircuitId::new(circuit_id),
1819 raw,
1820 })
1821}
1822
1823/// Parse Tor cell header
1824///
1825/// Tor v4 cell format:
1826/// - Circuit ID: 4 bytes
1827/// - Command: 1 byte
1828/// - Payload: 509 bytes (variable-length cells have length field)
1829///
1830/// Command types (tor-spec.txt section 3). Note these are the *real* numbers: an earlier
1831/// version of this table was shifted by one from command 7 onwards, so a client's CREATE2
1832/// (10) was read as CREATED2 and dropped as "unhandled", and this relay's own CREATED2
1833/// replies went out with the CREATE2 command byte.
1834/// - 0: PADDING
1835/// - 1: CREATE (obsolete)
1836/// - 2: CREATED (obsolete)
1837/// - 3: RELAY
1838/// - 4: DESTROY
1839/// - 5: CREATE_FAST (obsolete)
1840/// - 6: CREATED_FAST (obsolete)
1841/// - 7: VERSIONS (variable length - NOT handled, see below)
1842/// - 8: NETINFO
1843/// - 9: RELAY_EARLY
1844/// - 10: CREATE2
1845/// - 11: CREATED2
1846/// - 12: PADDING_NEGOTIATE
1847///
1848/// Commands 128+ (VPADDING, CERTS, AUTH_CHALLENGE, AUTHENTICATE, AUTHORIZE) and VERSIONS are
1849/// variable-length cells. They are framed by `take_cell` and dispatched to
1850/// `handle_variable_cell` before reaching here, so this function only ever sees the
1851/// fixed-length 514-byte form.
1852fn parse_tor_cell(data: &[u8]) -> Option<TorCellInfo> {
1853 if data.len() < 5 {
1854 return None;
1855 }
1856
1857 // Extract circuit ID (4 bytes, big-endian)
1858 let circuit_id_bytes: [u8; 4] = data[0..4].try_into().ok()?;
1859 let circuit_id = CircuitId::from_bytes(&circuit_id_bytes);
1860
1861 // Extract command byte
1862 let command = data[4];
1863
1864 // Map command to cell type
1865 let cell_type = match command {
1866 0 => "PADDING",
1867 1 => "CREATE",
1868 2 => "CREATED",
1869 3 => "RELAY",
1870 4 => "DESTROY",
1871 5 => "CREATE_FAST",
1872 6 => "CREATED_FAST",
1873 7 => "VERSIONS",
1874 8 => "NETINFO",
1875 9 => "RELAY_EARLY",
1876 10 => "CREATE2",
1877 11 => "CREATED2",
1878 12 => "PADDING_NEGOTIATE",
1879 _ => "UNKNOWN",
1880 };
1881
1882 Some(TorCellInfo {
1883 circuit_id,
1884 cell_type: cell_type.to_string(),
1885 })
1886}