pub mod actions;
pub mod circuit;
pub mod stream;
use anyhow::{Context, Result};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use tracing::{debug, error, info, trace, warn};
#[cfg(unix)]
use crate::llm::action_helper::call_llm;
#[cfg(unix)]
use crate::llm::ollama_client::OllamaClient;
#[cfg(unix)]
use crate::llm::ActionResult;
#[cfg(unix)]
use crate::protocol::Event;
#[cfg(unix)]
use crate::server::TorRelayProtocol;
#[cfg(unix)]
use crate::state::app_state::AppState;
#[cfg(unix)]
use actions::{TOR_RELAY_CIRCUIT_CREATED_EVENT, TOR_RELAY_RELAY_CELL_EVENT};
#[cfg(unix)]
use circuit::{CircuitId, CircuitManager, StreamId};
#[cfg(unix)]
use stream::{build_relay_cell, connect_to_target, end_reason, parse_begin_target, relay_command};
#[cfg(unix)]
use crate::logging::emit::Log;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
use tokio_rustls::rustls::ServerConfig;
use tokio_rustls::TlsAcceptor;
#[cfg(unix)]
type SharedWrite = Arc<Mutex<tokio::io::WriteHalf<tokio_rustls::server::TlsStream<TcpStream>>>>;
pub struct TorRelayServer;
#[cfg(unix)]
impl TorRelayServer {
pub async fn spawn_with_llm_actions(
listen_addr: SocketAddr,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
server_id: crate::state::ServerId,
) -> Result<SocketAddr> {
let (cert, key) = generate_tls_certificate()?;
let crypto_provider = tokio_rustls::rustls::crypto::ring::default_provider();
let tls_config = ServerConfig::builder_with_provider(Arc::new(crypto_provider))
.with_protocol_versions(&[
&tokio_rustls::rustls::version::TLS13,
&tokio_rustls::rustls::version::TLS12,
])
.expect("Valid TLS protocol versions")
.with_no_client_auth()
.with_single_cert(vec![cert], key)
.context("Failed to create TLS config")?;
let acceptor = TlsAcceptor::from(Arc::new(tls_config));
let listener = TcpListener::bind(listen_addr)
.await
.context("Failed to bind Tor Relay server")?;
let actual_addr = listener
.local_addr()
.context("Failed to get local address")?;
Log::new(Some(&status_tx)).info(format!(
"Tor Relay (OR protocol) server listening on {}",
actual_addr
));
let circuit_manager = Arc::new(CircuitManager::new());
let fingerprint = circuit_manager.identity_fingerprint();
let onion_key = *circuit_manager.onion_public_key().as_bytes();
Log::new(Some(&status_tx)).info(format!("Relay fingerprint: {}", hex::encode(fingerprint)));
Log::new(Some(&status_tx)).info(format!("Relay onion key: {}", hex::encode(onion_key)));
let protocol = Arc::new(TorRelayProtocol::new());
let task_registrar = app_state.clone();
let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONNECTIONS);
let accept_handle = tokio::spawn(async move {
loop {
match crate::server::accept_bounded::accept_bounded(
&listener,
&limiter,
CONNECTION_CAP_REFUSAL,
"Tor Relay",
Some(&status_tx),
)
.await
{
Ok((stream, remote_addr, permit)) => {
let connection_id = crate::server::connection::ConnectionId::new(
app_state.get_next_unified_id().await,
);
Log::new(Some(&status_tx)).debug(format!(
"Tor Relay connection {} from {}",
connection_id, remote_addr
));
let llm_clone = llm_client.clone();
let state_clone = app_state.clone();
let status_clone = status_tx.clone();
let acceptor_clone = acceptor.clone();
let protocol_clone = protocol.clone();
let circuit_mgr_clone = circuit_manager.clone();
let task_owner = app_state.clone();
task_owner
.spawn_server_task(server_id, async move {
let _permit = permit;
if let Err(e) = handle_tor_relay_connection(
stream,
connection_id,
server_id,
remote_addr,
acceptor_clone,
llm_clone,
state_clone,
status_clone,
protocol_clone,
circuit_mgr_clone,
)
.await
{
error!("Tor Relay connection error: {}", e);
}
})
.await;
}
Err(e) => {
Log::new(Some(&status_tx))
.error(format!("Failed to accept Tor Relay connection: {}", e));
}
}
}
});
task_registrar
.register_server_task(server_id, accept_handle)
.await;
Ok(actual_addr)
}
}
#[cfg(not(unix))]
impl TorRelayServer {
pub async fn spawn_with_llm_actions(
_listen_addr: SocketAddr,
_llm_client: OllamaClient,
_app_state: Arc<AppState>,
_status_tx: mpsc::UnboundedSender<String>,
_server_id: crate::state::ServerId,
) -> Result<SocketAddr> {
anyhow::bail!("Tor Relay feature not enabled")
}
}
fn generate_tls_certificate() -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>)> {
use rcgen::{CertificateParams, KeyPair};
let mut params = CertificateParams::new(vec!["tor-relay.local".to_string()])?;
params.distinguished_name = rcgen::DistinguishedName::new();
params
.distinguished_name
.push(rcgen::DnType::CommonName, "NetGet Tor Relay");
let key_pair = KeyPair::generate()?;
let cert = params.self_signed(&key_pair)?;
let cert_der = CertificateDer::from(cert.der().to_vec());
let key_der = PrivateKeyDer::Pkcs8(key_pair.serialize_der().into());
Ok((cert_der, key_der))
}
#[allow(clippy::too_many_arguments)]
async fn handle_tor_relay_connection(
stream: TcpStream,
connection_id: crate::server::connection::ConnectionId,
server_id: crate::state::ServerId,
remote_addr: SocketAddr,
acceptor: TlsAcceptor,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
protocol: Arc<TorRelayProtocol>,
circuit_manager: Arc<CircuitManager>,
) -> Result<()> {
let local_addr = stream
.local_addr()
.unwrap_or_else(|_| "0.0.0.0:0".parse().expect("0.0.0.0:0 parses"));
let now = crate::utils::clock::Instant::now();
app_state
.add_connection_to_server(
server_id,
crate::state::server::ConnectionState {
id: connection_id,
remote_addr,
local_addr,
bytes_sent: 0,
bytes_received: 0,
packets_sent: 0,
packets_received: 0,
last_activity: now,
status: crate::state::server::ConnectionStatus::Active,
status_changed_at: now,
protocol_info: crate::state::server::ProtocolConnectionInfo::empty(),
},
)
.await;
let _ = status_tx.send("__UPDATE_UI__".to_string());
let result = run_tor_relay_connection(
stream,
connection_id,
server_id,
remote_addr,
acceptor,
llm_client,
app_state.clone(),
status_tx.clone(),
protocol,
circuit_manager,
)
.await;
app_state
.remove_peer_handle(server_id, connection_id.as_u32())
.await;
app_state
.update_connection_status(
server_id,
connection_id,
crate::state::server::ConnectionStatus::Closed,
)
.await;
let _ = status_tx.send("__UPDATE_UI__".to_string());
result
}
#[allow(clippy::too_many_arguments)]
async fn run_tor_relay_connection(
stream: TcpStream,
connection_id: crate::server::connection::ConnectionId,
server_id: crate::state::ServerId,
remote_addr: SocketAddr,
acceptor: TlsAcceptor,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
protocol: Arc<TorRelayProtocol>,
circuit_manager: Arc<CircuitManager>,
) -> Result<()> {
let tls_stream = match acceptor.accept(stream).await {
Ok(s) => {
Log::new(Some(&status_tx)).info(format!("TLS handshake completed for {}", remote_addr));
s
}
Err(e) => {
Log::new(Some(&status_tx))
.warn(format!("TLS handshake failed for {}: {}", remote_addr, e));
return Err(e.into());
}
};
let (reader, writer) = tokio::io::split(tls_stream);
let write_half: SharedWrite = Arc::new(Mutex::new(writer));
let peer_rx = crate::server::peer_support::register_peer_channel(
&app_state,
server_id,
connection_id.as_u32(),
)
.await;
crate::server::peer_support::spawn_peer_command_task(
peer_rx,
protocol.clone(),
app_state.clone(),
server_id,
connection_id.as_u32(),
write_half.clone(),
status_tx.clone(),
);
let (outgoing_tx, outgoing_rx) = mpsc::unbounded_channel();
let mut session = TorRelaySession {
reader,
write_half: write_half.clone(),
connection_id,
server_id,
remote_addr,
llm_client,
app_state,
status_tx,
protocol,
circuit_manager,
outgoing_tx,
outgoing_rx,
};
let result = session.handle().await;
let _ = write_half.lock().await.shutdown().await;
result
}
struct TorRelaySession {
reader: tokio::io::ReadHalf<tokio_rustls::server::TlsStream<TcpStream>>,
write_half: SharedWrite,
connection_id: crate::server::connection::ConnectionId,
server_id: crate::state::ServerId,
remote_addr: SocketAddr,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
protocol: Arc<TorRelayProtocol>,
circuit_manager: Arc<CircuitManager>,
outgoing_tx: mpsc::UnboundedSender<Vec<u8>>,
outgoing_rx: mpsc::UnboundedReceiver<Vec<u8>>,
}
impl TorRelaySession {
async fn handle(&mut self) -> Result<()> {
debug!("Tor Relay session started for {}", self.remote_addr);
let mut inbuf: Vec<u8> = Vec::with_capacity(4096);
let mut read_buf = vec![0u8; 4096];
let mut seen_cell = false;
let mut expecting_versions = true;
loop {
while let Some(cell) = take_cell(&mut inbuf, expecting_versions) {
expecting_versions = false;
match cell {
FramedCell::Variable {
command, payload, ..
} => {
if let Some(response) = self.handle_variable_cell(command, &payload) {
self.write_counted(&response).await?;
}
}
FramedCell::Fixed { circuit_id, raw } => {
let Some(cell_info) = parse_tor_cell(&raw) else {
warn!("Failed to parse Tor cell from {}", self.remote_addr);
continue;
};
debug!(
"Tor cell: type={}, circuit_id={}",
cell_info.cell_type, cell_info.circuit_id
);
match self.handle_cell(cell_info, &raw).await {
Ok(Some(response)) => {
self.write_counted(&response).await?;
debug!("Sent response cell ({} bytes)", response.len());
}
Ok(None) => {}
Err(e) => {
error!("Failed to handle cell: {}", e);
let destroy = self.create_destroy_cell(circuit_id);
self.write_counted(&destroy).await?;
return Err(e);
}
}
}
}
}
let idle_bound = if seen_cell {
IDLE_BETWEEN_CELLS_TIMEOUT
} else {
FIRST_CELL_READ_TIMEOUT
};
let idle_deadline = tokio::time::sleep(idle_bound);
tokio::pin!(idle_deadline);
tokio::select! {
_ = &mut idle_deadline => {
Log::new(Some(&self.status_tx)).info(format!(
"Tor Relay peer {} sent nothing for {}s; closing idle connection",
self.remote_addr,
idle_bound.as_secs()
));
return Ok(());
}
read_result = self.reader.read(&mut read_buf) => {
match read_result {
Ok(0) => {
Log::new(Some(&self.status_tx)).info(format!(
"Tor Relay connection closed by {}",
self.remote_addr
));
return Ok(());
}
Ok(n) => {
seen_cell = true;
trace!("Received {} bytes from {}", n, self.remote_addr);
self.app_state
.update_connection_stats(
self.server_id,
self.connection_id,
Some(n as u64),
None,
Some(1),
None,
)
.await;
inbuf.extend_from_slice(&read_buf[..n]);
}
Err(e) => {
error!("Failed to read Tor cell from {}: {}", self.remote_addr, e);
return Err(e.into());
}
}
}
Some(cell) = self.outgoing_rx.recv() => {
trace!("Sending outgoing cell ({} bytes)", cell.len());
self.write_counted(&cell).await?;
}
}
}
}
async fn write_counted(&self, data: &[u8]) -> Result<()> {
{
let mut writer = self.write_half.lock().await;
writer.write_all(data).await?;
writer.flush().await?;
}
self.app_state
.update_connection_stats(
self.server_id,
self.connection_id,
None,
Some(data.len() as u64),
None,
Some(1),
)
.await;
Ok(())
}
fn handle_variable_cell(&self, command: u8, payload: &[u8]) -> Option<Vec<u8>> {
match command {
CELL_COMMAND_VERSIONS => {
let offered: Vec<u16> = payload
.chunks_exact(2)
.map(|c| u16::from_be_bytes([c[0], c[1]]))
.collect();
Log::new(Some(&self.status_tx))
.info(format!("Received VERSIONS cell offering {:?}", offered));
if !offered.contains(&LINK_PROTOCOL_VERSION) {
warn!(
"Peer offered link versions {:?}, none of which is {}",
offered, LINK_PROTOCOL_VERSION
);
}
let mut cell = Vec::with_capacity(7);
cell.extend_from_slice(&0u16.to_be_bytes());
cell.push(CELL_COMMAND_VERSIONS);
cell.extend_from_slice(&2u16.to_be_bytes());
cell.extend_from_slice(&LINK_PROTOCOL_VERSION.to_be_bytes());
Some(cell)
}
_ => {
warn!(
"Ignoring variable-length cell command {} ({} byte payload): the link \
handshake past VERSIONS is not implemented",
command,
payload.len()
);
None
}
}
}
async fn handle_cell(
&mut self,
cell_info: TorCellInfo,
cell_data: &[u8],
) -> Result<Option<Vec<u8>>> {
match cell_info.cell_type.as_str() {
"CREATE2" => self.handle_create2(cell_info.circuit_id, cell_data).await,
"RELAY" | "RELAY_EARLY" => self.handle_relay(cell_info.circuit_id, cell_data).await,
"DESTROY" => {
debug!(
"Received DESTROY for circuit {}",
cell_info.circuit_id.as_u32()
);
self.circuit_manager
.destroy_circuit(cell_info.circuit_id)
.await;
Ok(None)
}
"PADDING" => {
trace!("Received PADDING cell");
Ok(None)
}
_ => {
warn!("Unhandled cell type: {}", cell_info.cell_type);
Ok(None)
}
}
}
async fn handle_create2(
&mut self,
circuit_id: CircuitId,
cell_data: &[u8],
) -> Result<Option<Vec<u8>>> {
debug!("Processing CREATE2 for circuit {}", circuit_id.as_u32());
if cell_data.len() < 9 {
return Err(anyhow::anyhow!("CREATE2 cell too short"));
}
let htype = u16::from_be_bytes([cell_data[5], cell_data[6]]);
let hlen = u16::from_be_bytes([cell_data[7], cell_data[8]]);
if htype != 0x0002 {
return Err(anyhow::anyhow!("Unsupported handshake type: {}", htype));
}
if hlen != 84 {
return Err(anyhow::anyhow!("Invalid ntor handshake length: {}", hlen));
}
let handshake_start = 9;
let handshake_end = handshake_start + hlen as usize;
if cell_data.len() < handshake_end {
return Err(anyhow::anyhow!("Handshake data incomplete"));
}
let handshake_data = &cell_data[handshake_start..handshake_end];
let client_x: [u8; 32] = handshake_data[52..84].try_into()?;
let (y, auth) = self
.circuit_manager
.handle_create2(circuit_id, client_x)
.await?;
Log::new(Some(&self.status_tx)).info(format!(
"Circuit {} created successfully",
circuit_id.as_u32()
));
let stats = self.circuit_manager.get_relay_stats().await;
Log::new(Some(&self.status_tx)).debug(format!(
"Relay stats: {} circuits, {} streams, sent={} received={}",
stats.total_circuits,
stats.total_streams,
stats.total_bytes_sent,
stats.total_bytes_received
));
let event = Event::new(
&TOR_RELAY_CIRCUIT_CREATED_EVENT,
serde_json::json!({
"circuit_id": format!("0x{:08x}", circuit_id.as_u32()),
"client_ip": self.remote_addr.ip().to_string(),
}),
);
match call_llm(
&self.llm_client,
&self.app_state,
self.server_id,
Some(self.connection_id),
&event,
self.protocol.as_ref(),
)
.await
{
Ok(execution_result) => {
for message in execution_result.messages {
let _ = self.status_tx.send(message);
}
for protocol_result in execution_result.protocol_results {
match protocol_result {
ActionResult::Output(data) => {
info!(
"Tor relay circuit 0x{:08x}: CREATE2 answered by the model's \
own cell decision=model_answer",
circuit_id.as_u32()
);
return Ok(Some(data));
}
ActionResult::CloseConnection => {
info!(
"Tor relay circuit 0x{:08x}: model closed the connection on \
CREATE2 decision=model_reject",
circuit_id.as_u32()
);
self.circuit_manager.destroy_circuit(circuit_id).await;
return Err(anyhow::anyhow!("LLM requested close"));
}
_ => {}
}
}
}
Err(e) => {
Log::new(Some(&self.status_tx)).warn(format!(
"Tor relay circuit 0x{:08x}: LLM call failed on CREATE2 ({}) - sent DESTROY \
(reason 2 INTERNAL) decision=fail_closed_llm_error",
circuit_id.as_u32(),
e
));
self.circuit_manager.destroy_circuit(circuit_id).await;
return Ok(Some(self.create_destroy_cell_with_reason(
circuit_id,
DESTROY_REASON_INTERNAL,
)));
}
}
info!(
"Tor relay circuit 0x{:08x}: CREATED2 sent decision=model_answer",
circuit_id.as_u32()
);
let mut response = Vec::with_capacity(71);
response.extend_from_slice(&circuit_id.to_bytes());
response.push(11); response.extend_from_slice(&64u16.to_be_bytes()); response.extend_from_slice(&y);
response.extend_from_slice(&auth);
response.resize(514, 0);
Ok(Some(response))
}
async fn handle_relay(
&mut self,
circuit_id: CircuitId,
cell_data: &[u8],
) -> Result<Option<Vec<u8>>> {
trace!("Processing RELAY cell for circuit {}", circuit_id.as_u32());
let mut relay_payload = cell_data[5..514].to_vec();
self.circuit_manager
.decrypt_relay_cell(circuit_id, &mut relay_payload)
.await?;
let _ = self.circuit_manager.record_received(circuit_id, 509).await;
let send_circuit_sendme = self
.circuit_manager
.record_relay_received(circuit_id)
.await
.unwrap_or(false);
if send_circuit_sendme {
debug!(
"Sending circuit-level SENDME for circuit {}",
circuit_id.as_u32()
);
let sendme_cell = build_relay_cell(
circuit_id.as_u32(),
0, relay_command::SENDME,
&[],
);
let mut encrypted = sendme_cell.clone();
self.circuit_manager
.encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
.await?;
let _ = self.outgoing_tx.send(encrypted);
}
if relay_payload.len() < 11 {
return Err(anyhow::anyhow!("RELAY cell too short"));
}
let relay_cmd = relay_payload[0];
let recognized = u16::from_be_bytes([relay_payload[1], relay_payload[2]]);
let stream_id_u16 = u16::from_be_bytes([relay_payload[3], relay_payload[4]]);
let stream_id = StreamId::new(stream_id_u16);
let length = u16::from_be_bytes([relay_payload[9], relay_payload[10]]) as usize;
if 11 + length > relay_payload.len() {
warn!(
"RELAY cell on circuit {} declares {} bytes of data but only {} remain; dropping",
circuit_id.as_u32(),
length,
relay_payload.len().saturating_sub(11)
);
return Ok(None);
}
let data = &relay_payload[11..11 + length];
if recognized != 0 {
trace!("RELAY cell not recognized, dropping");
return Ok(None);
}
let relay_cmd_name = match relay_cmd {
relay_command::BEGIN => "BEGIN",
relay_command::DATA => "DATA",
relay_command::END => "END",
relay_command::CONNECTED => "CONNECTED",
relay_command::SENDME => "SENDME",
relay_command::EXTEND => "EXTEND",
relay_command::EXTENDED => "EXTENDED",
relay_command::TRUNCATE => "TRUNCATE",
relay_command::TRUNCATED => "TRUNCATED",
relay_command::DROP => "DROP",
relay_command::RESOLVE => "RESOLVE",
relay_command::RESOLVED => "RESOLVED",
relay_command::BEGIN_DIR => "BEGIN_DIR",
_ => "UNKNOWN",
};
debug!(
"RELAY cell: command={} ({}), stream={}, length={}",
relay_cmd,
relay_cmd_name,
stream_id.as_u16(),
length
);
match relay_cmd {
relay_command::BEGIN => {
return self.handle_begin_cell(circuit_id, stream_id, data).await;
}
relay_command::BEGIN_DIR => {
return self.handle_begin_dir_cell(circuit_id, stream_id).await;
}
relay_command::DATA => {
return self.handle_data_cell(circuit_id, stream_id, data).await;
}
relay_command::END => {
return self.handle_end_cell(circuit_id, stream_id, data).await;
}
relay_command::SENDME => {
return self.handle_sendme_cell(circuit_id, stream_id).await;
}
_ => {
let event = Event::new(
&TOR_RELAY_RELAY_CELL_EVENT,
serde_json::json!({
"circuit_id": format!("0x{:08x}", circuit_id.as_u32()),
"relay_command": relay_cmd_name,
"stream_id": stream_id.as_u16(),
"length": length,
"client_ip": self.remote_addr.ip().to_string(),
}),
);
match call_llm(
&self.llm_client,
&self.app_state,
self.server_id,
Some(self.connection_id),
&event,
self.protocol.as_ref(),
)
.await
{
Ok(execution_result) => {
for message in execution_result.messages {
let _ = self.status_tx.send(message);
}
for protocol_result in execution_result.protocol_results {
match protocol_result {
ActionResult::Output(data) => {
debug!(
"Tor relay circuit 0x{:08x}: RELAY {} answered \
decision=model_answer",
circuit_id.as_u32(),
relay_cmd_name
);
return Ok(Some(data));
}
ActionResult::CloseConnection => {
info!(
"Tor relay circuit 0x{:08x}: model closed the \
connection on RELAY {} decision=model_reject",
circuit_id.as_u32(),
relay_cmd_name
);
return Err(anyhow::anyhow!("LLM requested close"));
}
_ => {}
}
}
debug!(
"Tor relay circuit 0x{:08x}: RELAY {} left unanswered by the model \
decision=model_silent",
circuit_id.as_u32(),
relay_cmd_name
);
}
Err(e) => {
Log::new(Some(&self.status_tx)).warn(format!(
"Tor relay circuit 0x{:08x}: LLM call failed on RELAY {} ({}) - sent \
DESTROY (reason 2 INTERNAL) decision=fail_closed_llm_error",
circuit_id.as_u32(),
relay_cmd_name,
e
));
if crate::llm::is_overload_error(&e) {
warn!(
"Tor relay circuit {} destroyed: LLM capacity exhausted",
circuit_id.as_u32()
);
}
self.circuit_manager.destroy_circuit(circuit_id).await;
return Ok(Some(self.create_destroy_cell_with_reason(
circuit_id,
DESTROY_REASON_INTERNAL,
)));
}
}
}
}
Ok(None)
}
async fn handle_begin_cell(
&mut self,
circuit_id: CircuitId,
stream_id: StreamId,
data: &[u8],
) -> Result<Option<Vec<u8>>> {
let target = parse_begin_target(data)?;
Log::new(Some(&self.status_tx)).info(format!(
"BEGIN stream {} on circuit {} to {}",
stream_id.as_u16(),
circuit_id.as_u32(),
target
));
if let Err(e) = self
.circuit_manager
.create_stream(circuit_id, stream_id, target.clone())
.await
{
Log::new(Some(&self.status_tx)).warn(format!(
"Refusing BEGIN stream {} on circuit {}: {}",
stream_id.as_u16(),
circuit_id.as_u32(),
e
));
let end_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::END,
&[end_reason::RESOURCE_LIMIT],
);
let mut encrypted = end_cell.clone();
self.circuit_manager
.encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
.await?;
let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
return Ok(Some(encrypted));
}
match connect_to_target(&target).await {
Ok(tcp_stream) => {
Log::new(Some(&self.status_tx)).info(format!(
"Connected to {} for stream {}",
target,
stream_id.as_u16()
));
self.circuit_manager
.set_stream_active(circuit_id, stream_id, tcp_stream)
.await?;
let connected_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::CONNECTED,
&[],
);
let mut encrypted = connected_cell.clone();
self.circuit_manager
.encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
.await?;
let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
self.spawn_stream_forwarder(circuit_id, stream_id, self.outgoing_tx.clone())
.await?;
Ok(Some(encrypted))
}
Err(e) => {
Log::new(Some(&self.status_tx))
.warn(format!("Failed to connect to {}: {}", target, e));
let _ = self
.circuit_manager
.close_stream(circuit_id, stream_id)
.await;
let end_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::END,
&[end_reason::CONNECT_REFUSED],
);
let mut encrypted = end_cell.clone();
self.circuit_manager
.encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
.await?;
let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
Ok(Some(encrypted))
}
}
}
async fn handle_begin_dir_cell(
&mut self,
circuit_id: CircuitId,
stream_id: StreamId,
) -> Result<Option<Vec<u8>>> {
Log::new(Some(&self.status_tx)).info(format!(
"BEGIN_DIR stream {} on circuit {} (directory request over circuit)",
stream_id.as_u16(),
circuit_id.as_u32()
));
self.circuit_manager
.create_directory_stream(circuit_id, stream_id)
.await?;
info!(
"Directory stream {} ready on circuit {}",
stream_id.as_u16(),
circuit_id.as_u32()
);
let connected_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::CONNECTED,
&[],
);
let mut encrypted = connected_cell.clone();
self.circuit_manager
.encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
.await?;
Ok(Some(encrypted))
}
async fn handle_data_cell(
&mut self,
circuit_id: CircuitId,
stream_id: StreamId,
data: &[u8],
) -> Result<Option<Vec<u8>>> {
trace!(
"DATA cell for stream {} ({} bytes)",
stream_id.as_u16(),
data.len()
);
let send_stream_sendme = self
.circuit_manager
.record_stream_data_received(circuit_id, stream_id)
.await
.unwrap_or(false);
if send_stream_sendme {
debug!(
"Sending stream-level SENDME for stream {}",
stream_id.as_u16()
);
let sendme_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::SENDME,
&[],
);
let mut encrypted = sendme_cell.clone();
self.circuit_manager
.encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
.await?;
let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
let _ = self.outgoing_tx.send(encrypted);
}
if self
.circuit_manager
.is_directory_stream(circuit_id, stream_id)
.await?
{
return self
.handle_directory_data(circuit_id, stream_id, data)
.await;
}
if let Some(connection) = self
.circuit_manager
.get_stream_writer(circuit_id, stream_id)
.await?
{
let mut conn = connection.lock().await;
if let Err(e) = conn.write_all(data).await {
error!("Failed to write to stream {}: {}", stream_id.as_u16(), e);
drop(conn); let _ = self
.circuit_manager
.close_stream(circuit_id, stream_id)
.await;
let end_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::END,
&[end_reason::MISC],
);
let mut encrypted = end_cell.clone();
self.circuit_manager
.encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
.await?;
let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
return Ok(Some(encrypted));
}
trace!(
"Forwarded {} bytes to stream {}",
data.len(),
stream_id.as_u16()
);
} else {
warn!("Stream {} not found or not active", stream_id.as_u16());
}
Ok(None)
}
async fn handle_directory_data(
&mut self,
circuit_id: CircuitId,
stream_id: StreamId,
data: &[u8],
) -> Result<Option<Vec<u8>>> {
self.circuit_manager
.append_directory_data(circuit_id, stream_id, data)
.await?;
let request_data = self
.circuit_manager
.get_directory_request(circuit_id, stream_id)
.await?;
if let Some(pos) = request_data.windows(4).position(|w| w == b"\r\n\r\n") {
let request_str = String::from_utf8_lossy(&request_data[..pos]);
let first_line = request_str.lines().next().unwrap_or("");
debug!("Directory request: {}", first_line);
let parts: Vec<&str> = first_line.split_whitespace().collect();
let (_method, path) = if parts.len() >= 2 {
(parts[0], parts[1])
} else {
("GET", "/")
};
let consensus = if path.contains("consensus") {
Self::generate_test_consensus()
} else {
b"HTTP/1.0 404 Not Found\r\n\r\n".to_vec()
};
info!(
"Serving {} response ({} bytes) for directory path: {}",
if path.contains("consensus") {
"consensus"
} else {
"404"
},
consensus.len(),
path
);
self.send_directory_response(circuit_id, stream_id, &consensus)
.await?;
let end_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::END,
&[end_reason::DONE],
);
let mut encrypted = end_cell.clone();
self.circuit_manager
.encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
.await?;
return Ok(Some(encrypted));
}
Ok(None)
}
async fn send_directory_response(
&mut self,
circuit_id: CircuitId,
stream_id: StreamId,
data: &[u8],
) -> Result<()> {
const MAX_RELAY_PAYLOAD: usize = 498;
for chunk in data.chunks(MAX_RELAY_PAYLOAD) {
let data_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::DATA,
chunk,
);
let mut encrypted = data_cell.clone();
self.circuit_manager
.encrypt_relay_cell(circuit_id, &mut encrypted[5..514])
.await?;
self.outgoing_tx.send(encrypted)?;
let _ = self.circuit_manager.record_sent(circuit_id, 509).await;
}
Ok(())
}
async fn handle_end_cell(
&mut self,
circuit_id: CircuitId,
stream_id: StreamId,
data: &[u8],
) -> Result<Option<Vec<u8>>> {
let reason = if data.is_empty() {
end_reason::DONE
} else {
data[0]
};
Log::new(Some(&self.status_tx)).debug(format!(
"END stream {} (reason: {})",
stream_id.as_u16(),
reason
));
let _ = self
.circuit_manager
.close_stream(circuit_id, stream_id)
.await;
Ok(None)
}
async fn handle_sendme_cell(
&mut self,
circuit_id: CircuitId,
stream_id: StreamId,
) -> Result<Option<Vec<u8>>> {
if stream_id.as_u16() == 0 {
debug!(
"Received circuit-level SENDME for circuit {}",
circuit_id.as_u32()
);
let _ = self
.circuit_manager
.process_circuit_sendme(circuit_id)
.await;
} else {
debug!(
"Received stream-level SENDME for stream {}",
stream_id.as_u16()
);
let _ = self
.circuit_manager
.process_stream_sendme(circuit_id, stream_id)
.await;
}
Ok(None)
}
fn generate_test_consensus() -> Vec<u8> {
use crate::utils::clock::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let hour = now - (now % 3600);
let valid_after = chrono::DateTime::from_timestamp(hour as i64, 0)
.unwrap()
.format("%Y-%m-%d %H:%M:%S")
.to_string();
let fresh_until = chrono::DateTime::from_timestamp((hour + 3600) as i64, 0)
.unwrap()
.format("%Y-%m-%d %H:%M:%S")
.to_string();
let valid_until = chrono::DateTime::from_timestamp((hour + 10800) as i64, 0)
.unwrap()
.format("%Y-%m-%d %H:%M:%S")
.to_string();
let consensus_body = format!(
"network-status-version 3\n\
vote-status consensus\n\
consensus-method 35\n\
valid-after {}\n\
fresh-until {}\n\
valid-until {}\n\
voting-delay 300 300\n\
client-versions 0.4.7.0-alpha-dev,0.4.8.0\n\
server-versions 0.4.7.0-alpha-dev,0.4.8.0\n\
known-flags Authority BadExit Exit Fast Guard HSDir NoEdConsensus Running Stable StaleDesc Sybil V2Dir Valid\n\
params CircuitPriorityHalflifeMsec=30000 DoSCircuitCreationEnabled=1 DoSConnectionEnabled=1\n\
r TestRelay1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 127.0.0.1 9001 127.0.0.1 9030\n\
s Exit Fast Guard HSDir Running Stable V2Dir Valid\n\
v Tor 0.4.8.0\n\
w Bandwidth=5000\n\
p accept 1-65535\n\
r TestRelay2 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= 127.0.0.2 9001 127.0.0.2 9030\n\
s Exit Fast Guard HSDir Running Stable V2Dir Valid\n\
v Tor 0.4.8.0\n\
w Bandwidth=5000\n\
p accept 1-65535\n\
r TestRelay3 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC= 127.0.0.3 9001 127.0.0.3 9030\n\
s Exit Fast Guard HSDir Running Stable V2Dir Valid\n\
v Tor 0.4.8.0\n\
w Bandwidth=5000\n\
p accept 1-65535\n\
r TestRelay4 DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD= 127.0.0.4 9001 127.0.0.4 9030\n\
s Exit Fast Guard HSDir Running Stable V2Dir Valid\n\
v Tor 0.4.8.0\n\
w Bandwidth=5000\n\
p accept 1-65535\n\
directory-footer\n\
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\
directory-signature 0000000000000000000000000000000000000000 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n\
-----BEGIN SIGNATURE-----\n\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n\
-----END SIGNATURE-----\n",
valid_after, fresh_until, valid_until
);
let http_response = format!(
"HTTP/1.0 200 OK\r\n\
Content-Type: text/plain\r\n\
Content-Length: {}\r\n\
\r\n\
{}",
consensus_body.len(),
consensus_body
);
http_response.into_bytes()
}
async fn spawn_stream_forwarder(
&self,
circuit_id: CircuitId,
stream_id: StreamId,
outgoing_tx: mpsc::UnboundedSender<Vec<u8>>,
) -> Result<()> {
let connection = self
.circuit_manager
.get_stream_reader(circuit_id, stream_id)
.await?
.ok_or_else(|| anyhow::anyhow!("Stream not found"))?;
let circuit_mgr = self.circuit_manager.clone();
let status_tx = self.status_tx.clone();
let task_owner = self.app_state.clone();
task_owner
.spawn_server_task(self.server_id, async move {
let mut buffer = vec![0u8; 498];
loop {
let bytes_read = {
let mut conn = connection.lock().await;
match conn.read(&mut buffer).await {
Ok(0) => {
debug!("Stream {} EOF from target", stream_id.as_u16());
break;
}
Ok(n) => n,
Err(e) => {
error!("Failed to read from stream {}: {}", stream_id.as_u16(), e);
break;
}
}
};
let mut data_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::DATA,
&buffer[..bytes_read],
);
if let Err(e) = circuit_mgr
.encrypt_relay_cell(circuit_id, &mut data_cell[5..514])
.await
{
error!(
"Failed to encrypt DATA cell for stream {}: {}",
stream_id.as_u16(),
e
);
break;
}
let _ = circuit_mgr.record_sent(circuit_id, 509).await;
if let Err(e) = outgoing_tx.send(data_cell) {
error!(
"Failed to send DATA cell for stream {}: {}",
stream_id.as_u16(),
e
);
break;
}
trace!(
"Forwarded {} bytes from stream {} back to client",
bytes_read,
stream_id.as_u16()
);
}
let mut end_cell = build_relay_cell(
circuit_id.as_u32(),
stream_id.as_u16(),
relay_command::END,
&[end_reason::DONE],
);
if let Ok(_) = circuit_mgr
.encrypt_relay_cell(circuit_id, &mut end_cell[5..514])
.await
{
let _ = circuit_mgr.record_sent(circuit_id, 509).await;
let _ = outgoing_tx.send(end_cell);
}
let _ = circuit_mgr.close_stream(circuit_id, stream_id).await;
Log::new(Some(&status_tx)).debug(format!("Stream {} closed", stream_id.as_u16()));
})
.await;
Ok(())
}
fn create_destroy_cell_with_reason(&self, circuit_id: CircuitId, reason: u8) -> Vec<u8> {
let mut cell = Vec::with_capacity(FIXED_CELL_LEN);
cell.extend_from_slice(&circuit_id.to_bytes());
cell.push(4); cell.push(reason);
cell.resize(FIXED_CELL_LEN, 0); cell
}
fn create_destroy_cell(&self, circuit_id: CircuitId) -> Vec<u8> {
self.create_destroy_cell_with_reason(circuit_id, DESTROY_REASON_PROTOCOL)
}
}
#[derive(Debug, Clone)]
struct TorCellInfo {
circuit_id: CircuitId,
cell_type: String,
}
const FIRST_CELL_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const IDLE_BETWEEN_CELLS_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(900);
const MAX_CONNECTIONS: usize = crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
const CONNECTION_CAP_REFUSAL: &[u8] = &[0x15, 0x03, 0x03, 0x00, 0x02, 0x02, 0x50];
const LINK_PROTOCOL_VERSION: u16 = 4;
const CELL_COMMAND_VERSIONS: u8 = 7;
const FIXED_CELL_LEN: usize = 514;
const DESTROY_REASON_PROTOCOL: u8 = 1;
const DESTROY_REASON_INTERNAL: u8 = 2;
#[derive(Debug)]
enum FramedCell {
Fixed { circuit_id: CircuitId, raw: Vec<u8> },
Variable {
#[allow(dead_code)]
circuit_id: u32,
command: u8,
payload: Vec<u8>,
},
}
fn take_cell(buf: &mut Vec<u8>, expecting_versions: bool) -> Option<FramedCell> {
if expecting_versions && buf.len() >= 3 && buf[2] == CELL_COMMAND_VERSIONS {
if buf.len() < 5 {
return None;
}
let length = u16::from_be_bytes([buf[3], buf[4]]) as usize;
if buf.len() < 5 + length {
return None;
}
let circuit_id = u16::from_be_bytes([buf[0], buf[1]]) as u32;
let payload = buf[5..5 + length].to_vec();
buf.drain(..5 + length);
return Some(FramedCell::Variable {
circuit_id,
command: CELL_COMMAND_VERSIONS,
payload,
});
}
if buf.len() < 5 {
return None;
}
let circuit_id = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
let command = buf[4];
if command >= 128 {
if buf.len() < 7 {
return None;
}
let length = u16::from_be_bytes([buf[5], buf[6]]) as usize;
if buf.len() < 7 + length {
return None;
}
let payload = buf[7..7 + length].to_vec();
buf.drain(..7 + length);
return Some(FramedCell::Variable {
circuit_id,
command,
payload,
});
}
if buf.len() < FIXED_CELL_LEN {
return None;
}
let raw = buf[..FIXED_CELL_LEN].to_vec();
buf.drain(..FIXED_CELL_LEN);
Some(FramedCell::Fixed {
circuit_id: CircuitId::new(circuit_id),
raw,
})
}
fn parse_tor_cell(data: &[u8]) -> Option<TorCellInfo> {
if data.len() < 5 {
return None;
}
let circuit_id_bytes: [u8; 4] = data[0..4].try_into().ok()?;
let circuit_id = CircuitId::from_bytes(&circuit_id_bytes);
let command = data[4];
let cell_type = match command {
0 => "PADDING",
1 => "CREATE",
2 => "CREATED",
3 => "RELAY",
4 => "DESTROY",
5 => "CREATE_FAST",
6 => "CREATED_FAST",
7 => "VERSIONS",
8 => "NETINFO",
9 => "RELAY_EARLY",
10 => "CREATE2",
11 => "CREATED2",
12 => "PADDING_NEGOTIATE",
_ => "UNKNOWN",
};
Some(TorCellInfo {
circuit_id,
cell_type: cell_type.to_string(),
})
}