vanguards_rs/
config.rs

1//! Configuration management for vanguards-rs.
2//!
3//! This module provides configuration parsing from TOML files, command-line arguments,
4//! and environment variables. Configuration is applied in order: defaults → config file →
5//! command-line arguments, with later sources overriding earlier ones.
6//!
7//! # Overview
8//!
9//! The configuration system supports multiple sources with clear precedence rules,
10//! allowing flexible deployment scenarios from simple defaults to complex multi-source
11//! configurations.
12//!
13//! # Configuration Flow
14//!
15//! ```text
16//! ┌─────────────────────────────────────────────────────────────────────────────┐
17//! │                        Configuration Loading Flow                           │
18//! └─────────────────────────────────────────────────────────────────────────────┘
19//!
20//!     ┌─────────────────┐
21//!     │ Config::default │ ◄── Start with sensible defaults
22//!     └────────┬────────┘
23//!              │
24//!              ▼
25//!     ┌─────────────────┐     ┌─────────────────┐
26//!     │ Config file     │ ◄───│ vanguards.conf  │  (TOML format)
27//!     │ exists?         │     │ or --config     │
28//!     └────────┬────────┘     └─────────────────┘
29//!              │
30//!         Yes  │  No
31//!       ┌──────┴───────┐
32//!       ▼              │
33//! ┌─────────────┐      │
34//! │ Merge file  │      │
35//! │ settings    │      │
36//! └──────┬──────┘      │
37//!        │             │
38//!        └──────┬──────┘
39//!               │
40//!               ▼
41//!     ┌─────────────────┐     ┌─────────────────┐
42//!     │ Apply CLI args  │ ◄───│ --control-port  │
43//!     │ (override)      │     │ --state, etc.   │
44//!     └────────┬────────┘     └─────────────────┘
45//!              │
46//!              ▼
47//!     ┌─────────────────┐     ┌─────────────────┐
48//!     │ Apply env vars  │ ◄───│ VANGUARDS_STATE │
49//!     │ (if not set)    │     │ VANGUARDS_CONFIG│
50//!     └────────┬────────┘     └─────────────────┘
51//!              │
52//!              ▼
53//!     ┌─────────────────┐
54//!     │ Validate &      │
55//!     │ resolve hosts   │
56//!     └────────┬────────┘
57//!              │
58//!              ▼
59//!     ┌─────────────────┐
60//!     │ Final Config    │ ◄── Ready to use
61//!     └─────────────────┘
62//! ```
63//!
64//! # Configuration Sources
65//!
66//! | Source | Priority | Description |
67//! |--------|----------|-------------|
68//! | Defaults | Lowest | Built-in sensible defaults |
69//! | Config File | Medium | TOML file (`--config` or `VANGUARDS_CONFIG`) |
70//! | Environment | High | `VANGUARDS_STATE`, `VANGUARDS_CONFIG` |
71//! | CLI Arguments | Highest | Command-line flags override all |
72//!
73//! # Example Configuration File
74//!
75//! ```toml
76//! # Connection settings
77//! control_ip = "127.0.0.1"
78//! control_port = 9051
79//! # control_socket = "/run/tor/control"  # Alternative: Unix socket
80//! # control_pass = "my_password"         # If using password auth
81//!
82//! # File paths
83//! state_file = "vanguards.state"
84//!
85//! # Logging
86//! loglevel = "notice"  # debug, info, notice, warn, error
87//! # logfile = "/var/log/vanguards.log"  # Optional: log to file
88//! # logfile = ":syslog:"                 # Optional: log to syslog
89//!
90//! # Component toggles
91//! enable_vanguards = true
92//! enable_bandguards = true
93//! enable_rendguard = true
94//! enable_logguard = true
95//! enable_cbtverify = false
96//! enable_pathverify = false
97//!
98//! # Operational settings
99//! close_circuits = true
100//! one_shot_vanguards = false
101//! # retry_limit = 10  # Optional: limit reconnection attempts
102//!
103//! [vanguards]
104//! num_layer1_guards = 2   # 0 = use Tor default
105//! num_layer2_guards = 4
106//! num_layer3_guards = 8
107//! min_layer2_lifetime_hours = 24
108//! max_layer2_lifetime_hours = 1080  # 45 days
109//! min_layer3_lifetime_hours = 1
110//! max_layer3_lifetime_hours = 48
111//!
112//! [bandguards]
113//! circ_max_megabytes = 0           # 0 = disabled
114//! circ_max_age_hours = 24
115//! circ_max_hsdesc_kilobytes = 30
116//! circ_max_serv_intro_kilobytes = 0
117//! circ_max_disconnected_secs = 30
118//! conn_max_disconnected_secs = 15
119//!
120//! [rendguard]
121//! use_global_start_count = 1000
122//! use_scale_at_count = 20000
123//! use_relay_start_count = 100
124//! use_max_use_to_bw_ratio = 5.0
125//! use_max_consensus_weight_churn = 1.0
126//! close_circuits_on_overuse = true
127//!
128//! [logguard]
129//! protocol_warns = true
130//! dump_limit = 25
131//! dump_level = "notice"
132//! ```
133//!
134//! # What This Module Does NOT Do
135//!
136//! - **Runtime reconfiguration**: Config is loaded once at startup
137//! - **Config file watching**: Changes require restart
138//! - **Encrypted config files**: Passwords are stored in plaintext
139//!
140//! # See Also
141//!
142//! - [`LogLevel`] for logging configuration
143//! - [`VanguardsConfig`] for vanguard-specific settings
144//! - [`BandguardsConfig`] for bandwidth monitoring settings
145//! - [`RendguardConfig`] for rendezvous point monitoring settings
146//! - [`LogguardConfig`] for log monitoring settings
147//! - [`CliArgs`] for command-line argument parsing
148
149use clap::Parser;
150use serde::{Deserialize, Serialize};
151use std::net::{IpAddr, ToSocketAddrs};
152use std::path::PathBuf;
153
154use crate::error::{Error, Result};
155
156/// Log level for vanguards-rs output.
157///
158/// These levels control the verbosity of log output. From most to least verbose:
159/// [`Debug`](LogLevel::Debug) > [`Info`](LogLevel::Info) > [`Notice`](LogLevel::Notice) >
160/// [`Warn`](LogLevel::Warn) > [`Error`](LogLevel::Error)
161///
162/// # Example
163///
164/// ```rust
165/// use vanguards_rs::LogLevel;
166///
167/// let level = LogLevel::Notice;
168/// assert!(level < LogLevel::Warn);
169/// ```
170#[derive(
171    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
172)]
173#[serde(rename_all = "lowercase")]
174pub enum LogLevel {
175    /// Low-level debugging information.
176    Debug,
177    /// Informational messages about normal operation.
178    Info,
179    /// Notable events that may be of interest.
180    #[default]
181    Notice,
182    /// Warning conditions that don't prevent operation.
183    Warn,
184    /// Error conditions that may impair functionality.
185    Error,
186}
187
188impl std::fmt::Display for LogLevel {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        match self {
191            LogLevel::Debug => write!(f, "DEBUG"),
192            LogLevel::Info => write!(f, "INFO"),
193            LogLevel::Notice => write!(f, "NOTICE"),
194            LogLevel::Warn => write!(f, "WARN"),
195            LogLevel::Error => write!(f, "ERROR"),
196        }
197    }
198}
199
200impl std::str::FromStr for LogLevel {
201    type Err = Error;
202
203    fn from_str(s: &str) -> Result<Self> {
204        match s.to_uppercase().as_str() {
205            "DEBUG" => Ok(LogLevel::Debug),
206            "INFO" => Ok(LogLevel::Info),
207            "NOTICE" => Ok(LogLevel::Notice),
208            "WARN" | "WARNING" => Ok(LogLevel::Warn),
209            "ERROR" | "ERR" => Ok(LogLevel::Error),
210            _ => Err(Error::Config(format!("invalid log level: {}", s))),
211        }
212    }
213}
214
215/// Vanguard-specific configuration options.
216///
217/// Controls the number of guards at each layer and their rotation lifetimes.
218/// These settings directly affect the security/performance tradeoff of vanguard
219/// protection.
220///
221/// # Guard Layers
222///
223/// ```text
224/// ┌─────────────────────────────────────────────────────────────────┐
225/// │                    Vanguard Guard Layers                        │
226/// └─────────────────────────────────────────────────────────────────┘
227///
228///   Client ──▶ Layer1 ──▶ Layer2 ──▶ Layer3 ──▶ Rendezvous Point
229///              (Entry)    (Middle)   (Middle)
230///
231///   Layer1: Entry guards (managed by Tor, configurable count)
232///   Layer2: First vanguard layer (longer lifetime: 1-45 days)
233///   Layer3: Second vanguard layer (shorter lifetime: 1-48 hours)
234/// ```
235///
236/// # Fields
237///
238/// | Field | Default | Description |
239/// |-------|---------|-------------|
240/// | `num_layer1_guards` | 2 | Entry guards (0 = Tor default) |
241/// | `num_layer2_guards` | 4 | Layer2 vanguard count |
242/// | `num_layer3_guards` | 8 | Layer3 vanguard count |
243/// | `layer1_lifetime_days` | 0 | Entry guard lifetime (0 = Tor default) |
244/// | `min_layer2_lifetime_hours` | 24 | Minimum layer2 lifetime |
245/// | `max_layer2_lifetime_hours` | 1080 | Maximum layer2 lifetime (45 days) |
246/// | `min_layer3_lifetime_hours` | 1 | Minimum layer3 lifetime |
247/// | `max_layer3_lifetime_hours` | 48 | Maximum layer3 lifetime |
248///
249/// # Security Considerations
250///
251/// - **More guards** = Better anonymity but more exposure to malicious relays
252/// - **Longer lifetimes** = Better protection against guard discovery but slower recovery from compromise
253/// - **Shorter lifetimes** = Faster recovery but more vulnerable to timing attacks
254///
255/// # Example
256///
257/// ```rust
258/// use vanguards_rs::VanguardsConfig;
259///
260/// let mut config = VanguardsConfig::default();
261///
262/// // Increase guard counts for higher security
263/// config.num_layer2_guards = 6;
264/// config.num_layer3_guards = 12;
265///
266/// // Extend layer2 lifetime for better protection
267/// config.max_layer2_lifetime_hours = 2160; // 90 days
268/// ```
269///
270/// # See Also
271///
272/// - [`Config`] - Main configuration struct
273/// - [`VanguardState`](crate::VanguardState) - Runtime guard state
274#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
275pub struct VanguardsConfig {
276    /// Number of layer1 (entry) guards. 0 means use Tor default.
277    #[serde(default = "default_num_layer1_guards")]
278    pub num_layer1_guards: u8,
279    /// Number of layer2 guards.
280    #[serde(default = "default_num_layer2_guards")]
281    pub num_layer2_guards: u8,
282    /// Number of layer3 guards.
283    #[serde(default = "default_num_layer3_guards")]
284    pub num_layer3_guards: u8,
285    /// Layer1 guard lifetime in days. 0 means use Tor default.
286    #[serde(default)]
287    pub layer1_lifetime_days: u16,
288    /// Minimum layer2 guard lifetime in hours.
289    #[serde(default = "default_min_layer2_lifetime_hours")]
290    pub min_layer2_lifetime_hours: u32,
291    /// Maximum layer2 guard lifetime in hours.
292    #[serde(default = "default_max_layer2_lifetime_hours")]
293    pub max_layer2_lifetime_hours: u32,
294    /// Minimum layer3 guard lifetime in hours.
295    #[serde(default = "default_min_layer3_lifetime_hours")]
296    pub min_layer3_lifetime_hours: u32,
297    /// Maximum layer3 guard lifetime in hours.
298    #[serde(default = "default_max_layer3_lifetime_hours")]
299    pub max_layer3_lifetime_hours: u32,
300}
301
302fn default_num_layer1_guards() -> u8 {
303    2
304}
305fn default_num_layer2_guards() -> u8 {
306    4
307}
308fn default_num_layer3_guards() -> u8 {
309    8
310}
311fn default_min_layer2_lifetime_hours() -> u32 {
312    24
313}
314fn default_max_layer2_lifetime_hours() -> u32 {
315    1080
316}
317fn default_min_layer3_lifetime_hours() -> u32 {
318    1
319}
320fn default_max_layer3_lifetime_hours() -> u32 {
321    48
322}
323
324impl Default for VanguardsConfig {
325    fn default() -> Self {
326        Self {
327            num_layer1_guards: default_num_layer1_guards(),
328            num_layer2_guards: default_num_layer2_guards(),
329            num_layer3_guards: default_num_layer3_guards(),
330            layer1_lifetime_days: 0,
331            min_layer2_lifetime_hours: default_min_layer2_lifetime_hours(),
332            max_layer2_lifetime_hours: default_max_layer2_lifetime_hours(),
333            min_layer3_lifetime_hours: default_min_layer3_lifetime_hours(),
334            max_layer3_lifetime_hours: default_max_layer3_lifetime_hours(),
335        }
336    }
337}
338
339/// Bandwidth monitoring configuration options.
340///
341/// Controls circuit bandwidth limits and disconnection warnings. These settings
342/// help detect bandwidth-based side-channel attacks that attempt to identify
343/// hidden services through traffic analysis.
344///
345/// # Attack Detection
346///
347/// Bandguards monitors for several attack patterns:
348///
349/// ```text
350/// ┌─────────────────────────────────────────────────────────────────┐
351/// │                  Bandwidth Attack Detection                     │
352/// └─────────────────────────────────────────────────────────────────┘
353///
354///   1. Circuit Size Attack
355///      ├── Monitor total bytes per circuit
356///      └── Alert/close if exceeds circ_max_megabytes
357///
358///   2. Circuit Age Attack
359///      ├── Track circuit creation time
360///      └── Alert/close if exceeds circ_max_age_hours
361///
362///   3. HSDIR Descriptor Attack
363///      ├── Monitor HSDIR circuit bandwidth
364///      └── Alert if exceeds circ_max_hsdesc_kilobytes
365///
366///   4. Connectivity Monitoring
367///      ├── Track disconnection duration
368///      └── Warn if exceeds threshold
369/// ```
370///
371/// # Fields
372///
373/// | Field | Default | Description |
374/// |-------|---------|-------------|
375/// | `circ_max_megabytes` | 0 | Max circuit size in MB (0 = disabled) |
376/// | `circ_max_age_hours` | 24 | Max circuit age in hours |
377/// | `circ_max_hsdesc_kilobytes` | 30 | Max HSDIR circuit size in KB |
378/// | `circ_max_serv_intro_kilobytes` | 0 | Max intro circuit size (0 = disabled) |
379/// | `circ_max_disconnected_secs` | 30 | Warn after N seconds disconnected |
380/// | `conn_max_disconnected_secs` | 15 | Warn after N seconds with no connections |
381///
382/// # Example
383///
384/// ```rust
385/// use vanguards_rs::BandguardsConfig;
386///
387/// let mut config = BandguardsConfig::default();
388///
389/// // Enable circuit size limiting
390/// config.circ_max_megabytes = 100;
391///
392/// // Reduce circuit age for higher security
393/// config.circ_max_age_hours = 12;
394/// ```
395///
396/// # See Also
397///
398/// - [`Config`] - Main configuration struct
399/// - [`BandwidthStats`](crate::BandwidthStats) - Runtime bandwidth statistics
400#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
401pub struct BandguardsConfig {
402    /// Maximum circuit size in megabytes. 0 disables this check.
403    #[serde(default)]
404    pub circ_max_megabytes: u64,
405    /// Maximum circuit age in hours.
406    #[serde(default = "default_circ_max_age_hours")]
407    pub circ_max_age_hours: u32,
408    /// Maximum HSDIR circuit size in kilobytes.
409    #[serde(default = "default_circ_max_hsdesc_kilobytes")]
410    pub circ_max_hsdesc_kilobytes: u32,
411    /// Maximum service intro circuit size in kilobytes. 0 disables.
412    #[serde(default)]
413    pub circ_max_serv_intro_kilobytes: u32,
414    /// Warn after this many seconds disconnected from circuits.
415    #[serde(default = "default_circ_max_disconnected_secs")]
416    pub circ_max_disconnected_secs: u32,
417    /// Warn after this many seconds with no connections.
418    #[serde(default = "default_conn_max_disconnected_secs")]
419    pub conn_max_disconnected_secs: u32,
420}
421
422fn default_circ_max_age_hours() -> u32 {
423    24
424}
425fn default_circ_max_hsdesc_kilobytes() -> u32 {
426    30
427}
428fn default_circ_max_disconnected_secs() -> u32 {
429    30
430}
431fn default_conn_max_disconnected_secs() -> u32 {
432    15
433}
434
435impl Default for BandguardsConfig {
436    fn default() -> Self {
437        Self {
438            circ_max_megabytes: 0,
439            circ_max_age_hours: default_circ_max_age_hours(),
440            circ_max_hsdesc_kilobytes: default_circ_max_hsdesc_kilobytes(),
441            circ_max_serv_intro_kilobytes: 0,
442            circ_max_disconnected_secs: default_circ_max_disconnected_secs(),
443            conn_max_disconnected_secs: default_conn_max_disconnected_secs(),
444        }
445    }
446}
447
448/// Rendezvous point monitoring configuration options.
449///
450/// Controls detection of rendezvous point overuse attacks. These attacks attempt
451/// to identify hidden services by observing which relays are frequently used as
452/// rendezvous points.
453///
454/// # Attack Detection
455///
456/// Rendguard tracks rendezvous point usage and compares it to expected bandwidth
457/// distribution:
458///
459/// ```text
460/// ┌─────────────────────────────────────────────────────────────────┐
461/// │                Rendezvous Point Overuse Detection               │
462/// └─────────────────────────────────────────────────────────────────┘
463///
464///   Expected Usage = Relay Bandwidth Weight / Total Network Bandwidth
465///
466///   Actual Usage = Times Used as Rend Point / Total Rend Point Uses
467///
468///   Overuse Ratio = Actual Usage / Expected Usage
469///
470///   If Overuse Ratio > use_max_use_to_bw_ratio:
471///      └── Alert and optionally close circuits
472/// ```
473///
474/// # Fields
475///
476/// | Field | Default | Description |
477/// |-------|---------|-------------|
478/// | `use_global_start_count` | 1000 | Min total uses before checking |
479/// | `use_scale_at_count` | 20000 | Scale counts by half at this total |
480/// | `use_relay_start_count` | 100 | Min relay uses before checking |
481/// | `use_max_use_to_bw_ratio` | 5.0 | Max ratio of use to bandwidth |
482/// | `use_max_consensus_weight_churn` | 1.0 | Max consensus weight churn % |
483/// | `close_circuits_on_overuse` | true | Close circuits on overuse detection |
484///
485/// # Example
486///
487/// ```rust
488/// use vanguards_rs::RendguardConfig;
489///
490/// let mut config = RendguardConfig::default();
491///
492/// // More aggressive detection
493/// config.use_max_use_to_bw_ratio = 3.0;
494///
495/// // Require more samples before alerting
496/// config.use_global_start_count = 2000;
497/// config.use_relay_start_count = 200;
498/// ```
499///
500/// # See Also
501///
502/// - [`Config`] - Main configuration struct
503/// - [`RendGuard`](crate::RendGuard) - Runtime rendezvous tracking
504#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
505pub struct RendguardConfig {
506    /// Minimum total uses before checking for overuse.
507    #[serde(default = "default_use_global_start_count")]
508    pub use_global_start_count: u32,
509    /// Scale counts by half when reaching this total.
510    #[serde(default = "default_use_scale_at_count")]
511    pub use_scale_at_count: u32,
512    /// Minimum relay uses before checking for overuse.
513    #[serde(default = "default_use_relay_start_count")]
514    pub use_relay_start_count: u32,
515    /// Maximum ratio of use to bandwidth weight.
516    #[serde(default = "default_use_max_use_to_bw_ratio")]
517    pub use_max_use_to_bw_ratio: f64,
518    /// Maximum consensus weight churn percentage.
519    #[serde(default = "default_use_max_consensus_weight_churn")]
520    pub use_max_consensus_weight_churn: f64,
521    /// Close circuits on rendezvous point overuse.
522    #[serde(default = "default_close_circuits_on_overuse")]
523    pub close_circuits_on_overuse: bool,
524}
525
526fn default_use_global_start_count() -> u32 {
527    1000
528}
529fn default_use_scale_at_count() -> u32 {
530    20000
531}
532fn default_use_relay_start_count() -> u32 {
533    100
534}
535fn default_use_max_use_to_bw_ratio() -> f64 {
536    5.0
537}
538fn default_use_max_consensus_weight_churn() -> f64 {
539    1.0
540}
541fn default_close_circuits_on_overuse() -> bool {
542    true
543}
544
545impl Default for RendguardConfig {
546    fn default() -> Self {
547        Self {
548            use_global_start_count: default_use_global_start_count(),
549            use_scale_at_count: default_use_scale_at_count(),
550            use_relay_start_count: default_use_relay_start_count(),
551            use_max_use_to_bw_ratio: default_use_max_use_to_bw_ratio(),
552            use_max_consensus_weight_churn: default_use_max_consensus_weight_churn(),
553            close_circuits_on_overuse: default_close_circuits_on_overuse(),
554        }
555    }
556}
557
558/// Log monitoring configuration options.
559///
560/// Controls Tor log buffering and protocol warning handling.
561#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
562pub struct LogguardConfig {
563    /// Enable ProtocolWarnings in Tor.
564    #[serde(default = "default_protocol_warns")]
565    pub protocol_warns: bool,
566    /// Maximum number of log entries to buffer.
567    #[serde(default = "default_dump_limit")]
568    pub dump_limit: usize,
569    /// Minimum log level to buffer.
570    #[serde(default)]
571    pub dump_level: LogLevel,
572}
573
574fn default_protocol_warns() -> bool {
575    true
576}
577fn default_dump_limit() -> usize {
578    25
579}
580
581impl Default for LogguardConfig {
582    fn default() -> Self {
583        Self {
584            protocol_warns: default_protocol_warns(),
585            dump_limit: default_dump_limit(),
586            dump_level: LogLevel::Notice,
587        }
588    }
589}
590
591/// Main configuration struct for vanguards-rs.
592///
593/// This struct contains all configuration options for the vanguards-rs library
594/// and CLI application. Configuration can be loaded from TOML files, command-line
595/// arguments, and environment variables.
596///
597/// # Fields Overview
598///
599/// ## Connection Settings
600///
601/// | Field | Type | Default | Description |
602/// |-------|------|---------|-------------|
603/// | `control_ip` | `String` | `"127.0.0.1"` | Tor control port IP address |
604/// | `control_port` | `Option<u16>` | `None` | Tor control port number |
605/// | `control_socket` | `Option<PathBuf>` | `None` | Unix socket path (alternative to TCP) |
606/// | `control_pass` | `Option<String>` | `None` | Control port password |
607///
608/// ## File Settings
609///
610/// | Field | Type | Default | Description |
611/// |-------|------|---------|-------------|
612/// | `state_file` | `PathBuf` | `"vanguards.state"` | Vanguard state persistence file |
613///
614/// ## Logging Settings
615///
616/// | Field | Type | Default | Description |
617/// |-------|------|---------|-------------|
618/// | `loglevel` | `LogLevel` | `Notice` | Log verbosity level |
619/// | `logfile` | `Option<String>` | `None` | Log destination (file, `:syslog:`, or stdout) |
620///
621/// ## Component Toggles
622///
623/// | Field | Type | Default | Description |
624/// |-------|------|---------|-------------|
625/// | `enable_vanguards` | `bool` | `true` | Enable vanguard selection |
626/// | `enable_bandguards` | `bool` | `true` | Enable bandwidth monitoring |
627/// | `enable_rendguard` | `bool` | `true` | Enable rendezvous point monitoring |
628/// | `enable_logguard` | `bool` | `true` | Enable log monitoring |
629/// | `enable_cbtverify` | `bool` | `false` | Enable circuit build timeout verification |
630/// | `enable_pathverify` | `bool` | `false` | Enable path verification |
631///
632/// ## Operational Settings
633///
634/// | Field | Type | Default | Description |
635/// |-------|------|---------|-------------|
636/// | `close_circuits` | `bool` | `true` | Close circuits on detected attacks |
637/// | `one_shot_vanguards` | `bool` | `false` | Set vanguards and exit immediately |
638/// | `retry_limit` | `Option<u32>` | `None` | Max reconnection attempts (None = infinite) |
639///
640/// # Example
641///
642/// ## Creating Default Configuration
643///
644/// ```rust
645/// use vanguards_rs::Config;
646///
647/// let config = Config::default();
648/// assert_eq!(config.control_ip, "127.0.0.1");
649/// assert!(config.enable_vanguards);
650/// ```
651///
652/// ## Loading from File
653///
654/// ```rust,no_run
655/// use vanguards_rs::Config;
656/// use std::path::Path;
657///
658/// let config = Config::from_file(Path::new("vanguards.conf"))?;
659/// # Ok::<(), vanguards_rs::Error>(())
660/// ```
661///
662/// ## Programmatic Configuration
663///
664/// ```rust
665/// use vanguards_rs::{Config, LogLevel};
666/// use std::path::PathBuf;
667///
668/// let mut config = Config::default();
669/// config.control_port = Some(9051);
670/// config.loglevel = LogLevel::Debug;
671/// config.state_file = PathBuf::from("/var/lib/tor/vanguards.state");
672/// config.enable_cbtverify = true;
673///
674/// // Validate before use
675/// config.validate().expect("Invalid configuration");
676/// ```
677///
678/// # Validation
679///
680/// Call [`validate()`](Config::validate) to check configuration consistency:
681///
682/// - Layer lifetime ranges must be valid (min ≤ max)
683/// - Ratio values must be positive
684/// - Churn values must be non-negative
685///
686/// # See Also
687///
688/// - [`VanguardsConfig`] - Vanguard-specific settings
689/// - [`BandguardsConfig`] - Bandwidth monitoring settings
690/// - [`RendguardConfig`] - Rendezvous point monitoring settings
691/// - [`LogguardConfig`] - Log monitoring settings
692/// - [`CliArgs`] - Command-line argument parsing
693/// - [`load_config`] - Configuration loading function
694#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
695pub struct Config {
696    /// IP address of the Tor control port.
697    #[serde(default = "default_control_ip")]
698    pub control_ip: String,
699    /// Port number of the Tor control port.
700    #[serde(default)]
701    pub control_port: Option<u16>,
702    /// Path to the Tor control socket.
703    #[serde(default)]
704    pub control_socket: Option<PathBuf>,
705    /// Password for Tor control authentication.
706    #[serde(default)]
707    pub control_pass: Option<String>,
708    /// Path to the vanguard state file.
709    #[serde(default = "default_state_file")]
710    pub state_file: PathBuf,
711    /// Log level for output.
712    #[serde(default)]
713    pub loglevel: LogLevel,
714    /// Log file path. None for stdout, ":syslog:" for syslog.
715    #[serde(default)]
716    pub logfile: Option<String>,
717    /// Maximum reconnection attempts. None for infinite.
718    #[serde(default)]
719    pub retry_limit: Option<u32>,
720    /// Set vanguards and exit immediately.
721    #[serde(default)]
722    pub one_shot_vanguards: bool,
723    /// Close circuits on detected attacks.
724    #[serde(default = "default_close_circuits")]
725    pub close_circuits: bool,
726    /// Enable vanguard selection.
727    #[serde(default = "default_enable_vanguards")]
728    pub enable_vanguards: bool,
729    /// Enable bandwidth monitoring.
730    #[serde(default = "default_enable_bandguards")]
731    pub enable_bandguards: bool,
732    /// Enable rendezvous point monitoring.
733    #[serde(default = "default_enable_rendguard")]
734    pub enable_rendguard: bool,
735    /// Enable log monitoring.
736    #[serde(default = "default_enable_logguard")]
737    pub enable_logguard: bool,
738    /// Enable circuit build timeout verification.
739    #[serde(default)]
740    pub enable_cbtverify: bool,
741    /// Enable path verification.
742    #[serde(default)]
743    pub enable_pathverify: bool,
744    /// Vanguard-specific configuration.
745    #[serde(default)]
746    pub vanguards: VanguardsConfig,
747    /// Bandwidth monitoring configuration.
748    #[serde(default)]
749    pub bandguards: BandguardsConfig,
750    /// Rendezvous point monitoring configuration.
751    #[serde(default)]
752    pub rendguard: RendguardConfig,
753    /// Log monitoring configuration.
754    #[serde(default)]
755    pub logguard: LogguardConfig,
756}
757
758fn default_control_ip() -> String {
759    "127.0.0.1".to_string()
760}
761fn default_state_file() -> PathBuf {
762    PathBuf::from("vanguards.state")
763}
764fn default_close_circuits() -> bool {
765    true
766}
767fn default_enable_vanguards() -> bool {
768    true
769}
770fn default_enable_bandguards() -> bool {
771    true
772}
773fn default_enable_rendguard() -> bool {
774    true
775}
776fn default_enable_logguard() -> bool {
777    true
778}
779
780impl Default for Config {
781    fn default() -> Self {
782        Self {
783            control_ip: default_control_ip(),
784            control_port: None,
785            control_socket: None,
786            control_pass: None,
787            state_file: default_state_file(),
788            loglevel: LogLevel::default(),
789            logfile: None,
790            retry_limit: None,
791            one_shot_vanguards: false,
792            close_circuits: default_close_circuits(),
793            enable_vanguards: default_enable_vanguards(),
794            enable_bandguards: default_enable_bandguards(),
795            enable_rendguard: default_enable_rendguard(),
796            enable_logguard: default_enable_logguard(),
797            enable_cbtverify: false,
798            enable_pathverify: false,
799            vanguards: VanguardsConfig::default(),
800            bandguards: BandguardsConfig::default(),
801            rendguard: RendguardConfig::default(),
802            logguard: LogguardConfig::default(),
803        }
804    }
805}
806
807impl Config {
808    /// Load configuration from a TOML file.
809    ///
810    /// # Errors
811    ///
812    /// Returns [`Error::Io`] if the file cannot be read.
813    /// Returns [`Error::Config`] if the TOML is invalid.
814    pub fn from_file(path: &std::path::Path) -> Result<Self> {
815        let content = std::fs::read_to_string(path)?;
816        toml::from_str(&content).map_err(|e| Error::Config(e.to_string()))
817    }
818
819    /// Serialize configuration to TOML string.
820    ///
821    /// # Errors
822    ///
823    /// Returns [`Error::Config`] if serialization fails.
824    pub fn to_toml(&self) -> Result<String> {
825        toml::to_string_pretty(self).map_err(|e| Error::Config(e.to_string()))
826    }
827
828    /// Validate configuration values.
829    ///
830    /// Checks that all configuration values are within acceptable ranges
831    /// and that required fields are present.
832    ///
833    /// # Errors
834    ///
835    /// Returns [`Error::Config`] if validation fails.
836    pub fn validate(&self) -> Result<()> {
837        if self.vanguards.min_layer2_lifetime_hours > self.vanguards.max_layer2_lifetime_hours {
838            return Err(Error::Config(
839                "min_layer2_lifetime_hours must be <= max_layer2_lifetime_hours".to_string(),
840            ));
841        }
842        if self.vanguards.min_layer3_lifetime_hours > self.vanguards.max_layer3_lifetime_hours {
843            return Err(Error::Config(
844                "min_layer3_lifetime_hours must be <= max_layer3_lifetime_hours".to_string(),
845            ));
846        }
847        if self.rendguard.use_max_use_to_bw_ratio <= 0.0 {
848            return Err(Error::Config(
849                "use_max_use_to_bw_ratio must be positive".to_string(),
850            ));
851        }
852        if self.rendguard.use_max_consensus_weight_churn < 0.0 {
853            return Err(Error::Config(
854                "use_max_consensus_weight_churn must be non-negative".to_string(),
855            ));
856        }
857        Ok(())
858    }
859
860    /// Resolve hostname to IP address if control_ip is a domain name.
861    ///
862    /// # Errors
863    ///
864    /// Returns [`Error::Config`] if hostname resolution fails.
865    pub fn resolve_control_ip(&mut self) -> Result<()> {
866        if self.control_ip.parse::<IpAddr>().is_err() {
867            let addr = format!("{}:0", self.control_ip)
868                .to_socket_addrs()
869                .map_err(|e| {
870                    Error::Config(format!(
871                        "failed to resolve hostname {}: {}",
872                        self.control_ip, e
873                    ))
874                })?
875                .next()
876                .ok_or_else(|| {
877                    Error::Config(format!(
878                        "no addresses found for hostname {}",
879                        self.control_ip
880                    ))
881                })?;
882            self.control_ip = addr.ip().to_string();
883        }
884        Ok(())
885    }
886}
887
888/// Command-line arguments for vanguards-rs.
889///
890/// This struct is used by clap to parse command-line arguments. Arguments override
891/// configuration file values, allowing runtime customization without modifying config files.
892///
893/// # Configuration Precedence
894///
895/// Configuration is applied in the following order (later sources override earlier):
896/// 1. Built-in defaults
897/// 2. Configuration file (TOML)
898/// 3. Environment variables (`VANGUARDS_STATE`, `VANGUARDS_CONFIG`)
899/// 4. Command-line arguments
900///
901/// # Usage
902///
903/// ```text
904/// vanguards-rs [OPTIONS]
905/// ```
906///
907/// # Options
908///
909/// ## Connection Options
910///
911/// | Option | Description |
912/// |--------|-------------|
913/// | `--control-ip <IP>` | IP address of the Tor control port (default: 127.0.0.1) |
914/// | `--control-port <PORT>` | Tor control port number (typically 9051) |
915/// | `--control-socket <PATH>` | Path to Tor control socket (e.g., /run/tor/control) |
916/// | `--control-pass <PASS>` | Tor control port password for authentication |
917///
918/// ## File Options
919///
920/// | Option | Description |
921/// |--------|-------------|
922/// | `--state <FILE>` | Path to the vanguard state file [env: VANGUARDS_STATE] |
923/// | `--config <FILE>` | Path to configuration file [env: VANGUARDS_CONFIG] [default: vanguards.conf] |
924/// | `--generate_config <FILE>` | Write default config to file and exit |
925///
926/// ## Logging Options
927///
928/// | Option | Description |
929/// |--------|-------------|
930/// | `--loglevel <LEVEL>` | Log verbosity: DEBUG, INFO, NOTICE, WARN, ERROR |
931/// | `--logfile <FILE>` | Log to file instead of stdout (use ":syslog:" for syslog) |
932///
933/// ## Component Control
934///
935/// | Option | Description |
936/// |--------|-------------|
937/// | `--disable-vanguards` | Disable vanguard selection |
938/// | `--disable-bandguards` | Disable bandwidth monitoring |
939/// | `--disable-rendguard` | Disable rendezvous point monitoring |
940/// | `--disable-logguard` | Disable log monitoring |
941/// | `--enable-cbtverify` | Enable circuit build timeout verification |
942/// | `--enable-pathverify` | Enable path verification |
943///
944/// ## Operational Options
945///
946/// | Option | Description |
947/// |--------|-------------|
948/// | `--retry-limit <N>` | Reconnection attempt limit (default: infinite) |
949/// | `--one-shot-vanguards` | Set vanguards and exit immediately |
950///
951/// ## Help Options
952///
953/// | Option | Description |
954/// |--------|-------------|
955/// | `-h, --help` | Print help (see a summary with '-h') |
956/// | `-V, --version` | Print version |
957///
958/// # Examples
959///
960/// Connect to Tor via control port:
961/// ```bash
962/// vanguards-rs --control-ip 127.0.0.1 --control-port 9051
963/// ```
964///
965/// Connect via Unix socket with password:
966/// ```bash
967/// vanguards-rs --control-socket /run/tor/control --control-pass mypassword
968/// ```
969///
970/// Generate a default configuration file:
971/// ```bash
972/// vanguards-rs --generate_config vanguards.conf
973/// ```
974///
975/// Run with custom state file and debug logging:
976/// ```bash
977/// vanguards-rs --state /var/lib/tor/vanguards.state --loglevel DEBUG
978/// ```
979///
980/// Disable specific components:
981/// ```bash
982/// vanguards-rs --disable-bandguards --disable-logguard
983/// ```
984///
985/// One-shot mode (set vanguards and exit):
986/// ```bash
987/// vanguards-rs --one-shot-vanguards
988/// ```
989///
990/// # Environment Variables
991///
992/// - `VANGUARDS_STATE`: Path to the vanguard state file (equivalent to `--state`)
993/// - `VANGUARDS_CONFIG`: Path to configuration file (equivalent to `--config`)
994///
995/// # See Also
996///
997/// - [`Config`] for the full configuration structure
998/// - [`load_config`] for the configuration loading function
999#[derive(Parser, Debug)]
1000#[command(name = "vanguards-rs")]
1001#[command(about = "Enhanced security for Tor hidden services")]
1002#[command(version)]
1003#[command(
1004    long_about = "vanguards-rs provides enhanced security for Tor hidden services through \
1005    persistent vanguard relay selection, bandwidth monitoring, rendezvous point protection, \
1006    circuit build timeout verification, path verification, and log monitoring."
1007)]
1008pub struct CliArgs {
1009    /// Path to the vanguard state file.
1010    ///
1011    /// The state file stores persistent vanguard selections and rendguard statistics.
1012    /// If not specified, defaults to "vanguards.state" in the current directory.
1013    #[arg(long = "state", env = "VANGUARDS_STATE")]
1014    pub state_file: Option<PathBuf>,
1015
1016    /// Write default config to file and exit.
1017    ///
1018    /// Generates a TOML configuration file with all default values and documentation.
1019    /// Useful for creating a starting point for customization.
1020    #[arg(long = "generate_config")]
1021    pub generate_config: Option<PathBuf>,
1022
1023    /// Log verbosity (DEBUG, INFO, NOTICE, WARN, ERROR).
1024    ///
1025    /// Controls the amount of output. DEBUG is most verbose, ERROR is least.
1026    /// Default is NOTICE.
1027    #[arg(long)]
1028    pub loglevel: Option<String>,
1029
1030    /// Log to file instead of stdout (use ":syslog:" for syslog).
1031    ///
1032    /// By default, logs go to stdout. Specify a file path to redirect logs,
1033    /// or use the special value ":syslog:" to send logs to the system logger.
1034    #[arg(long)]
1035    pub logfile: Option<String>,
1036
1037    /// Path to configuration file.
1038    ///
1039    /// TOML configuration file containing all settings. Command-line arguments
1040    /// override values from this file.
1041    #[arg(
1042        long = "config",
1043        env = "VANGUARDS_CONFIG",
1044        default_value = "vanguards.conf"
1045    )]
1046    pub config_file: PathBuf,
1047
1048    /// IP address of the Tor control port.
1049    ///
1050    /// Can be an IPv4 address, IPv6 address, or hostname (will be resolved).
1051    /// Default is 127.0.0.1.
1052    #[arg(long)]
1053    pub control_ip: Option<String>,
1054
1055    /// Tor control port number.
1056    ///
1057    /// The TCP port where Tor's control interface is listening.
1058    /// Typically 9051 for the Tor daemon.
1059    #[arg(long)]
1060    pub control_port: Option<u16>,
1061
1062    /// Path to Tor control socket.
1063    ///
1064    /// Unix domain socket path for Tor control connection.
1065    /// Takes precedence over TCP connection if specified.
1066    /// Common paths: /run/tor/control, /var/run/tor/control
1067    #[arg(long)]
1068    pub control_socket: Option<PathBuf>,
1069
1070    /// Tor control port password.
1071    ///
1072    /// Password for HashedControlPassword authentication.
1073    /// If not provided and required, will prompt interactively.
1074    #[arg(long)]
1075    pub control_pass: Option<String>,
1076
1077    /// Reconnection attempt limit (default: infinite).
1078    ///
1079    /// Maximum number of times to attempt reconnection to Tor after
1080    /// connection loss. Set to 0 for infinite retries.
1081    #[arg(long)]
1082    pub retry_limit: Option<u32>,
1083
1084    /// Set vanguards and exit.
1085    ///
1086    /// Configure Tor with vanguard settings, save the configuration,
1087    /// and exit immediately. Useful for one-time setup.
1088    #[arg(long)]
1089    pub one_shot_vanguards: bool,
1090
1091    /// Disable vanguard selection.
1092    ///
1093    /// Prevents vanguards-rs from selecting and configuring vanguard relays.
1094    /// Other monitoring components will still run if enabled.
1095    #[arg(long)]
1096    pub disable_vanguards: bool,
1097
1098    /// Disable bandwidth monitoring.
1099    ///
1100    /// Disables the bandguards component that monitors circuit bandwidth
1101    /// for potential side-channel attacks.
1102    #[arg(long)]
1103    pub disable_bandguards: bool,
1104
1105    /// Disable rendezvous point monitoring.
1106    ///
1107    /// Disables the rendguard component that monitors rendezvous point
1108    /// usage for statistical anomalies.
1109    #[arg(long)]
1110    pub disable_rendguard: bool,
1111
1112    /// Disable log monitoring.
1113    ///
1114    /// Disables the logguard component that monitors Tor logs for
1115    /// security-relevant events.
1116    #[arg(long)]
1117    pub disable_logguard: bool,
1118
1119    /// Enable circuit build timeout verification.
1120    ///
1121    /// Enables the cbtverify component that monitors circuit construction
1122    /// timing for anomalies. Disabled by default.
1123    #[arg(long)]
1124    pub enable_cbtverify: bool,
1125
1126    /// Enable path verification.
1127    ///
1128    /// Enables the pathverify component that verifies circuit paths
1129    /// conform to vanguard configuration. Disabled by default.
1130    #[arg(long)]
1131    pub enable_pathverify: bool,
1132}
1133
1134impl CliArgs {
1135    /// Apply CLI arguments to a configuration, overriding values.
1136    pub fn apply_to(&self, config: &mut Config) {
1137        if let Some(ref state_file) = self.state_file {
1138            config.state_file = state_file.clone();
1139        }
1140        if let Some(ref loglevel) = self.loglevel {
1141            if let Ok(level) = loglevel.parse() {
1142                config.loglevel = level;
1143            }
1144        }
1145        if let Some(ref logfile) = self.logfile {
1146            config.logfile = Some(logfile.clone());
1147        }
1148        if let Some(ref control_ip) = self.control_ip {
1149            config.control_ip = control_ip.clone();
1150        }
1151        if let Some(control_port) = self.control_port {
1152            config.control_port = Some(control_port);
1153        }
1154        if let Some(ref control_socket) = self.control_socket {
1155            config.control_socket = Some(control_socket.clone());
1156        }
1157        if let Some(ref control_pass) = self.control_pass {
1158            config.control_pass = Some(control_pass.clone());
1159        }
1160        if let Some(retry_limit) = self.retry_limit {
1161            config.retry_limit = Some(retry_limit);
1162        }
1163        if self.one_shot_vanguards {
1164            config.one_shot_vanguards = true;
1165        }
1166        if self.disable_vanguards {
1167            config.enable_vanguards = false;
1168        }
1169        if self.disable_bandguards {
1170            config.enable_bandguards = false;
1171        }
1172        if self.disable_rendguard {
1173            config.enable_rendguard = false;
1174        }
1175        if self.disable_logguard {
1176            config.enable_logguard = false;
1177        }
1178        if self.enable_cbtverify {
1179            config.enable_cbtverify = true;
1180        }
1181        if self.enable_pathverify {
1182            config.enable_pathverify = true;
1183        }
1184    }
1185}
1186
1187/// Load configuration from file and CLI arguments.
1188///
1189/// This function implements the configuration loading order:
1190/// 1. Start with defaults
1191/// 2. Apply config file if it exists
1192/// 3. Apply CLI arguments (override)
1193///
1194/// # Errors
1195///
1196/// Returns [`Error::Config`] if configuration is invalid.
1197pub fn load_config(args: &CliArgs) -> Result<Config> {
1198    let mut config = Config::default();
1199
1200    if args.config_file.exists() {
1201        config = Config::from_file(&args.config_file)?;
1202    }
1203
1204    args.apply_to(&mut config);
1205    config.resolve_control_ip()?;
1206    config.validate()?;
1207
1208    Ok(config)
1209}