Skip to main content

trojan_config/
types.rs

1//! Configuration type definitions for server, TLS, WebSocket, auth, metrics, and logging.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::defaults::*;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ServerConfig {
11    pub listen: String,
12    pub fallback: String,
13    #[serde(default = "default_tcp_timeout_secs")]
14    pub tcp_idle_timeout_secs: u64,
15    #[serde(default = "default_udp_timeout_secs")]
16    pub udp_timeout_secs: u64,
17    #[serde(default = "default_max_udp_payload")]
18    pub max_udp_payload: usize,
19    #[serde(default = "default_max_udp_buffer_bytes")]
20    pub max_udp_buffer_bytes: usize,
21    #[serde(default = "default_max_header_bytes")]
22    pub max_header_bytes: usize,
23    /// Maximum concurrent connections (None = unlimited)
24    #[serde(default)]
25    pub max_connections: Option<usize>,
26    /// Per-IP rate limiting configuration
27    #[serde(default)]
28    pub rate_limit: Option<RateLimitConfig>,
29    /// Fallback connection pool configuration
30    #[serde(default)]
31    pub fallback_pool: Option<FallbackPoolConfig>,
32    /// Resource limits configuration
33    #[serde(default)]
34    pub resource_limits: Option<ResourceLimitsConfig>,
35    /// TCP socket options
36    #[serde(default)]
37    pub tcp: TcpConfig,
38    /// Named outbound connectors for rule-based routing.
39    #[serde(default)]
40    pub outbounds: HashMap<String, OutboundConfig>,
41    /// Rule-set providers (local file or remote URL).
42    #[serde(default, rename = "rule-providers")]
43    pub rule_providers: HashMap<String, RuleProviderConfig>,
44    /// Ordered routing rules (first match wins).
45    #[serde(default)]
46    pub rules: Vec<RouteRuleConfig>,
47    /// GeoIP database configuration for rule-based routing.
48    #[serde(default)]
49    pub geoip: Option<GeoipConfig>,
50}
51
52/// GeoIP MaxMind database configuration.
53///
54/// Loading priority: `path` > `url` > `source` (built-in CDN).
55/// When `auto_update` is true and no `path` is set, the database
56/// is periodically re-downloaded in the background.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct GeoipConfig {
59    /// Built-in source name (e.g., "geolite2-country", "dbip-city").
60    #[serde(default = "default_geoip_source")]
61    pub source: String,
62    /// Local file path (highest priority — skips download).
63    #[serde(default)]
64    pub path: Option<String>,
65    /// Custom remote URL (overrides the built-in CDN URL for `source`).
66    #[serde(default)]
67    pub url: Option<String>,
68    /// Enable automatic background updates.
69    #[serde(default = "default_geoip_auto_update")]
70    pub auto_update: bool,
71    /// Update interval in seconds (default: 7 days = 604800).
72    #[serde(default = "default_geoip_interval")]
73    pub interval: u64,
74    /// Cache file path for downloaded databases.
75    #[serde(default)]
76    pub cache_path: Option<String>,
77}
78
79impl Default for GeoipConfig {
80    fn default() -> Self {
81        Self {
82            source: default_geoip_source(),
83            path: None,
84            url: None,
85            auto_update: default_geoip_auto_update(),
86            interval: default_geoip_interval(),
87            cache_path: None,
88        }
89    }
90}
91
92fn default_geoip_source() -> String {
93    "geolite2-country".to_string()
94}
95
96fn default_geoip_auto_update() -> bool {
97    true
98}
99
100fn default_geoip_interval() -> u64 {
101    604800 // 7 days
102}
103
104/// GeoIP lookup result containing geographic information for an IP address.
105#[derive(Debug, Clone, Default, Serialize, Deserialize)]
106pub struct GeoResult {
107    /// ISO 3166-1 alpha-2 country code (e.g., "CN", "US").
108    pub country: String,
109    /// Region/state/province name (e.g., "Shanghai", "California").
110    pub region: String,
111    /// City name (e.g., "Shanghai", "Los Angeles").
112    pub city: String,
113    /// Autonomous System Number.
114    pub asn: u32,
115    /// ASN organization name (e.g., "China Telecom").
116    pub org: String,
117    /// Longitude coordinate.
118    pub longitude: f64,
119    /// Latitude coordinate.
120    pub latitude: f64,
121}
122
123/// TCP socket configuration options.
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct TcpConfig {
126    /// Disable Nagle's algorithm (TCP_NODELAY) for lower latency.
127    #[serde(default = "default_tcp_no_delay")]
128    pub no_delay: bool,
129    /// TCP Keep-Alive interval in seconds (0 = disabled).
130    #[serde(default = "default_tcp_keepalive_secs")]
131    pub keepalive_secs: u64,
132    /// Enable SO_REUSEPORT for multi-process load balancing.
133    #[serde(default = "default_tcp_reuse_port")]
134    pub reuse_port: bool,
135    /// Enable TCP Fast Open (requires kernel support).
136    #[serde(default = "default_tcp_fast_open")]
137    pub fast_open: bool,
138    /// TCP Fast Open queue length (server-side).
139    #[serde(default = "default_tcp_fast_open_qlen")]
140    pub fast_open_qlen: u32,
141    /// Prefer IPv4 addresses when resolving DNS for outbound connections.
142    #[serde(default = "default_tcp_prefer_ipv4")]
143    pub prefer_ipv4: bool,
144}
145
146impl Default for TcpConfig {
147    fn default() -> Self {
148        Self {
149            no_delay: default_tcp_no_delay(),
150            keepalive_secs: default_tcp_keepalive_secs(),
151            reuse_port: default_tcp_reuse_port(),
152            fast_open: default_tcp_fast_open(),
153            fast_open_qlen: default_tcp_fast_open_qlen(),
154            prefer_ipv4: default_tcp_prefer_ipv4(),
155        }
156    }
157}
158
159/// Configuration for fallback connection warm pool.
160///
161/// Warm pool semantics:
162/// - Pre-connects up to `max_idle` fresh connections in the background.
163/// - Connections are handed out once and NOT returned to the pool.
164/// - Pool is periodically refilled.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct FallbackPoolConfig {
167    /// Maximum idle connections to keep in pool.
168    #[serde(default = "default_pool_max_idle")]
169    pub max_idle: usize,
170    /// Maximum age of pooled connections in seconds.
171    #[serde(default = "default_pool_max_age_secs")]
172    pub max_age_secs: u64,
173    /// Warm-fill batch size per cycle (1..=max_idle).
174    #[serde(default = "default_pool_fill_batch")]
175    pub fill_batch: usize,
176    /// Delay (ms) between each connection attempt within a batch.
177    #[serde(default = "default_pool_fill_delay_ms")]
178    pub fill_delay_ms: u64,
179}
180
181/// Configuration for resource limits.
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct ResourceLimitsConfig {
184    /// Buffer size for TCP relay (bytes).
185    #[serde(default = "default_relay_buffer_size")]
186    pub relay_buffer_size: usize,
187    /// TCP socket send buffer size (SO_SNDBUF). If 0, uses OS default.
188    #[serde(default)]
189    pub tcp_send_buffer: usize,
190    /// TCP socket receive buffer size (SO_RCVBUF). If 0, uses OS default.
191    #[serde(default)]
192    pub tcp_recv_buffer: usize,
193    /// TCP listener backlog (pending connections queue size).
194    #[serde(default = "default_connection_backlog")]
195    pub connection_backlog: u32,
196}
197
198/// Rate limiting configuration for per-IP connection throttling.
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct RateLimitConfig {
201    /// Maximum new connections per IP within the time window.
202    #[serde(default = "default_rate_limit_max_connections")]
203    pub max_connections_per_ip: u32,
204    /// Time window in seconds for rate limiting.
205    #[serde(default = "default_rate_limit_window_secs")]
206    pub window_secs: u64,
207    /// Cleanup interval in seconds for expired entries.
208    #[serde(default = "default_rate_limit_cleanup_secs")]
209    pub cleanup_interval_secs: u64,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct TlsConfig {
214    /// Server certificate file path (PEM format).
215    pub cert: String,
216    /// Server private key file path (PEM format).
217    pub key: String,
218    /// ALPN protocols to advertise.
219    #[serde(default)]
220    pub alpn: Vec<String>,
221    /// Minimum TLS version (tls12, tls13). Default: tls12
222    #[serde(default = "default_min_tls_version")]
223    pub min_version: String,
224    /// Maximum TLS version (tls12, tls13). Default: tls13
225    #[serde(default = "default_max_tls_version")]
226    pub max_version: String,
227    /// Path to CA certificate for client authentication (mTLS).
228    /// If set, client certificates will be required and verified.
229    #[serde(default)]
230    pub client_ca: Option<String>,
231    /// Cipher suites to use. If empty, uses rustls defaults.
232    /// Example: ["TLS13_AES_256_GCM_SHA384", "TLS13_CHACHA20_POLY1305_SHA256"]
233    #[serde(default)]
234    pub cipher_suites: Vec<String>,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct WebSocketConfig {
239    #[serde(default = "default_ws_enabled")]
240    pub enabled: bool,
241    #[serde(default = "default_ws_mode")]
242    pub mode: String,
243    #[serde(default = "default_ws_path")]
244    pub path: String,
245    #[serde(default)]
246    pub host: Option<String>,
247    #[serde(default)]
248    pub listen: Option<String>,
249    #[serde(default = "default_ws_max_frame_bytes")]
250    pub max_frame_bytes: usize,
251}
252
253impl Default for WebSocketConfig {
254    fn default() -> Self {
255        Self {
256            enabled: default_ws_enabled(),
257            mode: default_ws_mode(),
258            path: default_ws_path(),
259            host: None,
260            listen: None,
261            max_frame_bytes: default_ws_max_frame_bytes(),
262        }
263    }
264}
265
266#[derive(Debug, Clone, Default, Serialize, Deserialize)]
267pub struct AuthConfig {
268    /// Simple password list (no user IDs).
269    /// ```toml
270    /// passwords = ["password1", "password2"]
271    /// ```
272    #[serde(default)]
273    pub passwords: Vec<String>,
274
275    /// User entries with explicit IDs.
276    /// ```toml
277    /// [[auth.users]]
278    /// id = "alice"
279    /// password = "secret1"
280    /// ```
281    #[serde(default)]
282    pub users: Vec<UserEntry>,
283
284    /// HTTP remote auth-worker URL.
285    /// ```toml
286    /// http_url = "https://auth.example.workers.dev"
287    /// ```
288    #[serde(default)]
289    pub http_url: Option<String>,
290
291    /// Bearer token for node authentication with the auth-worker.
292    #[serde(default)]
293    pub http_node_token: Option<String>,
294
295    /// Serialization codec for HTTP auth: "bincode" (default) or "json".
296    #[serde(default)]
297    pub http_codec: Option<String>,
298
299    /// HTTP auth cache TTL in seconds (default: 300 = 5 min).
300    /// Only applies when `http_url` is set.
301    #[serde(default = "default_http_cache_ttl_secs")]
302    pub http_cache_ttl_secs: u64,
303
304    /// HTTP auth stale-while-revalidate window in seconds (default: 600 = 10 min).
305    /// Stale cache entries are served immediately while revalidated in the background.
306    #[serde(default = "default_http_cache_stale_ttl_secs")]
307    pub http_cache_stale_ttl_secs: u64,
308
309    /// HTTP auth negative cache TTL in seconds (default: 10).
310    /// Invalid hashes are cached for this duration to prevent request flooding.
311    #[serde(default = "default_http_cache_neg_ttl_secs")]
312    pub http_cache_neg_ttl_secs: u64,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct UserEntry {
317    pub id: String,
318    pub password: String,
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize, Default)]
322pub struct MetricsConfig {
323    pub listen: Option<String>,
324    /// GeoIP database for per-country metrics labels (country-level).
325    #[serde(default)]
326    pub geoip: Option<GeoipConfig>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize, Default)]
330pub struct LoggingConfig {
331    /// Log level (trace, debug, info, warn, error).
332    pub level: Option<String>,
333    /// Log format: json, pretty, or compact. Default: pretty.
334    pub format: Option<String>,
335    /// Output target: stdout or stderr. Default: stderr.
336    pub output: Option<String>,
337    /// Per-module log level filters (e.g., {"trojan_auth": "debug", "rustls": "warn"}).
338    #[serde(default)]
339    pub filters: HashMap<String, String>,
340}
341
342/// Named outbound connector configuration.
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct OutboundConfig {
345    /// Outbound type: "trojan", "direct", or "reject".
346    #[serde(rename = "type")]
347    pub outbound_type: String,
348    /// Target address (required for trojan).
349    #[serde(default)]
350    pub addr: Option<String>,
351    /// Password (for trojan outbound).
352    #[serde(default)]
353    pub password: Option<String>,
354    /// SNI (for trojan outbound).
355    #[serde(default)]
356    pub sni: Option<String>,
357    /// Bind to specific local IP (for direct outbound).
358    #[serde(default)]
359    pub bind: Option<String>,
360}
361
362/// Rule-set provider configuration.
363#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct RuleProviderConfig {
365    /// Format: "surge" or "clash".
366    pub format: String,
367    /// Behavior: "domain", "ipcidr", "classical", or "domain-set".
368    #[serde(default)]
369    pub behavior: Option<String>,
370    /// Source: "file" or "http".
371    pub source: String,
372    /// Local file path.
373    #[serde(default)]
374    pub path: Option<String>,
375    /// Remote URL (for http source).
376    #[serde(default)]
377    pub url: Option<String>,
378    /// Update interval in seconds (for http source).
379    #[serde(default)]
380    pub interval: Option<u64>,
381}
382
383/// A single routing rule entry.
384#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct RouteRuleConfig {
386    /// Reference to a named rule-set provider.
387    #[serde(default, rename = "rule-set")]
388    pub rule_set: Option<String>,
389    /// Inline rule type: "GEOIP", "FINAL", "DOMAIN", "DOMAIN-SUFFIX", etc.
390    #[serde(default, rename = "type")]
391    pub rule_type: Option<String>,
392    /// Inline rule value (e.g., "CN" for GEOIP, "example.com" for DOMAIN).
393    #[serde(default)]
394    pub value: Option<String>,
395    /// Action: "DIRECT", "REJECT", or a named outbound.
396    pub outbound: String,
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402
403    #[test]
404    fn geoip_config_defaults() {
405        let cfg = GeoipConfig::default();
406        assert_eq!(cfg.source, "geolite2-country");
407        assert!(cfg.path.is_none());
408        assert!(cfg.url.is_none());
409        assert!(cfg.auto_update);
410        assert_eq!(cfg.interval, 604800);
411        assert!(cfg.cache_path.is_none());
412    }
413
414    #[test]
415    fn geoip_config_deserialize_minimal() {
416        let toml_str = r#"source = "dbip-city""#;
417        let cfg: GeoipConfig = toml::from_str(toml_str).unwrap();
418        assert_eq!(cfg.source, "dbip-city");
419        assert!(cfg.auto_update);
420        assert_eq!(cfg.interval, 604800);
421    }
422
423    #[test]
424    fn geoip_config_deserialize_full() {
425        let toml_str = r#"
426source = "geolite2-city"
427path = "/tmp/test.mmdb"
428url = "https://example.com/geo.mmdb"
429auto_update = false
430interval = 3600
431cache_path = "/tmp/cache.mmdb"
432"#;
433        let cfg: GeoipConfig = toml::from_str(toml_str).unwrap();
434        assert_eq!(cfg.source, "geolite2-city");
435        assert_eq!(cfg.path.as_deref(), Some("/tmp/test.mmdb"));
436        assert_eq!(cfg.url.as_deref(), Some("https://example.com/geo.mmdb"));
437        assert!(!cfg.auto_update);
438        assert_eq!(cfg.interval, 3600);
439        assert_eq!(cfg.cache_path.as_deref(), Some("/tmp/cache.mmdb"));
440    }
441
442    #[test]
443    fn geo_result_default() {
444        let r = GeoResult::default();
445        assert!(r.country.is_empty());
446        assert!(r.region.is_empty());
447        assert!(r.city.is_empty());
448        assert_eq!(r.asn, 0);
449        assert!(r.org.is_empty());
450        assert_eq!(r.longitude, 0.0);
451        assert_eq!(r.latitude, 0.0);
452    }
453
454    #[test]
455    fn geo_result_serde_roundtrip() {
456        let r = GeoResult {
457            country: "CN".into(),
458            region: "Shanghai".into(),
459            city: "Shanghai".into(),
460            asn: 4134,
461            org: "China Telecom".into(),
462            longitude: 121.47,
463            latitude: 31.23,
464        };
465        let json = serde_json::to_string(&r).unwrap();
466        let r2: GeoResult = serde_json::from_str(&json).unwrap();
467        assert_eq!(r2.country, "CN");
468        assert_eq!(r2.asn, 4134);
469        assert!((r2.longitude - 121.47).abs() < 0.001);
470    }
471
472    #[test]
473    fn metrics_config_default() {
474        let cfg = MetricsConfig::default();
475        assert!(cfg.listen.is_none());
476        assert!(cfg.geoip.is_none());
477    }
478
479    #[test]
480    fn metrics_config_with_geoip() {
481        let toml_str = r#"
482listen = "0.0.0.0:9100"
483
484[geoip]
485source = "dbip-country"
486"#;
487        let cfg: MetricsConfig = toml::from_str(toml_str).unwrap();
488        assert_eq!(cfg.listen.as_deref(), Some("0.0.0.0:9100"));
489        let geoip = cfg.geoip.unwrap();
490        assert_eq!(geoip.source, "dbip-country");
491    }
492}