Files
onion-transit/src/main.rs
2026-02-15 08:14:32 +01:00

212 lines
7.1 KiB
Rust

mod cli;
mod config;
mod error;
mod proxy;
mod router;
mod security;
mod status;
mod tor;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use clap::Parser;
use tracing::{error, info};
use crate::cli::{Cli, Command};
use crate::config::{AppConfig, LogTarget, SecurityMode};
use crate::proxy::socks5::Socks5Server;
use crate::tor::engine::TorEngine;
fn main() {
let cli = Cli::parse();
match cli.command {
Command::Start { config, i_know_what_im_doing } => {
let cfg = match AppConfig::load(&config) {
Ok(c) => c,
Err(e) => {
eprintln!("Error loading config: {e}");
std::process::exit(1);
}
};
init_logging(&cfg);
// Security startup validation
if let Err(e) = security::validate_startup(&cfg, i_know_what_im_doing) {
error!("{e}");
std::process::exit(1);
}
// Reduced security runtime gate
if cfg.security.mode == SecurityMode::Reduced && !i_know_what_im_doing {
error!(
"security.mode = \"reduced\" requires --i-know-what-im-doing flag. Aborting."
);
std::process::exit(1);
}
let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime");
rt.block_on(async move {
run_daemon(cfg).await;
});
}
Command::ConfigCheck {
config,
test_client_ip,
test_onion,
} => {
let cfg = match AppConfig::load(&config) {
Ok(c) => {
println!("Config OK: {}", config.display());
c
}
Err(e) => {
eprintln!("Config ERROR: {e}");
std::process::exit(1);
}
};
println!(" mode: {:?}", cfg.mode);
println!(" trust_domain: {:?}", cfg.trust_domain);
println!(" security: {:?}", cfg.security.mode);
println!(" socks5: {}", cfg.proxy.socks5_listen);
println!(" auth: {}", if cfg.proxy.auth.enabled { "enabled" } else { "disabled" });
println!(" onion profile: {:?}", cfg.tor.profiles.default_onion);
println!(" clearnet profile: {:?}", cfg.tor.profiles.default_clearnet);
println!();
security::dry_run_acl(
&cfg,
test_client_ip.as_deref(),
test_onion.as_deref(),
);
}
Command::Status { json, data_dir } => {
let dir = data_dir.unwrap_or_else(|| std::path::PathBuf::from("/var/lib/onion-transit/arti"));
match status::read_status_file(&dir) {
Some(s) => {
if json {
println!("{}", serde_json::to_string_pretty(&s).unwrap());
} else {
println!("Onion-Transit v{}", s.version);
println!(" uptime: {}s", s.uptime_secs);
println!(" active streams: {}", s.active_streams);
println!(" total streams: {}", s.total_streams);
println!(" bootstrap attempts: {}", s.bootstrap_attempts);
}
}
None => {
if json {
let status = serde_json::json!({
"error": "no running daemon found",
"status_file": status::status_file_path(&dir).display().to_string(),
});
println!("{}", serde_json::to_string_pretty(&status).unwrap());
} else {
eprintln!(
"No running daemon found. Status file: {}",
status::status_file_path(&dir).display()
);
eprintln!("Is onion-transit running with data_dir = {}?", dir.display());
}
std::process::exit(1);
}
}
}
}
}
/// Initialize tracing based on config.
fn init_logging(config: &AppConfig) {
use tracing_subscriber::{fmt, EnvFilter};
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(&config.logging.level));
match config.logging.target {
LogTarget::Stdout => {
fmt()
.with_env_filter(filter)
.with_target(false)
.init();
}
LogTarget::File => {
if let Some(ref path) = config.logging.file_path {
let dir = path.parent().unwrap_or(std::path::Path::new("."));
let filename = path
.file_name()
.unwrap_or(std::ffi::OsStr::new("onion-transit.log"));
let file_appender = tracing_appender::rolling::never(dir, filename);
fmt()
.with_env_filter(filter)
.with_writer(file_appender)
.with_ansi(false)
.init();
} else {
// Fallback to stdout if no file path configured
fmt().with_env_filter(filter).init();
}
}
LogTarget::Syslog => {
// Syslog: use stdout with JSON format as a reasonable approximation
// that integrates well with journald.
fmt()
.with_env_filter(filter)
.json()
.init();
}
}
}
/// Run the main daemon: bootstrap Tor, start SOCKS5 proxy, handle signals.
async fn run_daemon(config: AppConfig) {
let config = Arc::new(config);
// Bootstrap Arti
info!("Starting Onion-Transit daemon...");
let engine = match TorEngine::bootstrap(&config.tor).await {
Ok(e) => Arc::new(e),
Err(e) => {
error!("Failed to bootstrap Tor: {e}");
std::process::exit(1);
}
};
info!(
active_streams = engine.metrics.active_streams.load(Ordering::Relaxed),
total_streams = engine.metrics.total_streams.load(Ordering::Relaxed),
"Tor engine ready"
);
// Start status writer (writes status file every 5 seconds)
let start_time = std::time::Instant::now();
let status_metrics = Arc::clone(&engine.metrics);
let status_data_dir = config.tor.data_dir.clone();
tokio::spawn(async move {
status::status_writer_task(status_metrics, status_data_dir, start_time, 5).await;
});
// Start SOCKS5 proxy
let socks_server = Socks5Server::new(Arc::clone(&config), Arc::clone(&engine));
tokio::select! {
result = socks_server.run() => {
if let Err(e) = result {
error!("SOCKS5 server error: {e}");
}
}
_ = tokio::signal::ctrl_c() => {
info!("Received Ctrl+C, shutting down...");
}
}
info!(
total_streams = engine.metrics.total_streams.load(Ordering::Relaxed),
"Onion-Transit daemon stopped."
);
}