ostp/ostp-client/src/config.rs

262 lines
7.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
/// Client runtime configuration.
/// Constructed by the main binary from the unified `config.json`,
/// then passed into `runner::run_client`. All I/O happens in the
/// binary layer — this crate only owns the plain data structures.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientConfig {
pub mode: String,
#[serde(default)]
pub debug: bool,
pub ostp: OstpConfig,
pub local_proxy: LocalProxyConfig,
#[serde(default)]
pub transport: TransportConfig,
#[serde(default)]
pub exclusions: ExclusionConfig,
#[serde(default)]
pub multiplex: MultiplexConfig,
pub dns_server: Option<String>,
#[serde(default = "default_tun_stack")]
pub tun_stack: String,
#[serde(default)]
pub kill_switch: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gui: Option<serde_json::Value>,
}
fn default_tun_stack() -> String { "system".to_string() }
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ExclusionConfig {
#[serde(default)]
pub domains: Vec<String>,
#[serde(default)]
pub ips: Vec<String>,
#[serde(default)]
pub processes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiplexConfig {
pub enabled: bool,
pub sessions: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OstpConfig {
pub server_addr: String,
pub local_bind_addr: String,
#[serde(alias = "auth_token")]
pub access_key: String,
pub handshake_timeout_ms: u64,
pub io_timeout_ms: u64,
#[serde(default = "default_mtu")]
pub mtu: usize,
#[serde(default = "default_keepalive")]
pub keepalive_interval_sec: u64,
}
fn default_keepalive() -> u64 { 5 }
fn default_mtu() -> usize { 1140 }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocalProxyConfig {
pub bind_addr: String,
pub connect_timeout_ms: u64,
}
/// Transport layer configuration.
/// `mode` = "udp" (default) or "uot" (UDP over TCP с xHTTP-транспортом).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransportConfig {
/// "udp" or "uot"
#[serde(default = "default_transport_mode")]
pub mode: String,
/// TLS SNI and HTTP Host for xHTTP routing
#[serde(default)]
pub stealth_sni: String,
}
fn default_transport_mode() -> String { "udp".to_string() }
impl Default for TransportConfig {
fn default() -> Self {
Self {
mode: default_transport_mode(),
stealth_sni: String::new(),
}
}
}
impl Default for OstpConfig {
fn default() -> Self {
Self {
server_addr: "127.0.0.1:50000".to_string(),
local_bind_addr: "0.0.0.0:0".to_string(),
access_key: String::new(),
handshake_timeout_ms: 5000,
io_timeout_ms: 2500,
mtu: default_mtu(),
keepalive_interval_sec: default_keepalive(),
}
}
}
impl Default for LocalProxyConfig {
fn default() -> Self {
Self {
bind_addr: "127.0.0.1:1088".to_string(),
connect_timeout_ms: 15000,
}
}
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
mode: "proxy".to_string(),
debug: false,
ostp: OstpConfig::default(),
local_proxy: LocalProxyConfig::default(),
transport: TransportConfig::default(),
exclusions: ExclusionConfig::default(),
multiplex: MultiplexConfig::default(),
dns_server: None,
tun_stack: "system".to_string(),
kill_switch: false,
gui: None,
}
}
}
impl Default for MultiplexConfig {
fn default() -> Self {
Self {
enabled: false,
sessions: 1,
}
}
}
/// Unified shape of `config.json` as seen by the client.
/// Used only for hot-reloading (`BridgeCommand::ReloadConfig`).
#[derive(Debug, Deserialize)]
struct RawUnifiedConfig {
#[allow(dead_code)]
mode: String,
debug: Option<bool>,
server: Option<String>,
access_key: Option<String>,
mtu: Option<usize>,
socks5_bind: Option<String>,
tun: Option<RawTunSection>,
exclude: Option<RawExcludeSection>,
mux: Option<RawMuxSection>,
transport: Option<RawTransportSection>,
gui: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
struct RawTransportSection {
mode: Option<String>,
stealth_sni: Option<String>,
}
#[derive(Debug, Deserialize)]
struct RawTunSection {
enable: Option<bool>,
dns: Option<String>,
stack: Option<String>,
kill_switch: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct RawExcludeSection {
domains: Option<Vec<String>>,
ips: Option<Vec<String>>,
processes: Option<Vec<String>>,
}
#[derive(Debug, Deserialize)]
struct RawMuxSection {
enabled: Option<bool>,
sessions: Option<usize>,
}
impl ClientConfig {
/// Hot-reload from `config.json` placed next to the running binary.
/// Returns a new `ClientConfig` built from the unified JSON format.
pub fn reload_from_json_near_binary() -> Result<Self> {
let exe = std::env::current_exe().context("cannot resolve binary path")?;
let dir = exe.parent().context("cannot resolve binary directory")?;
let path = dir.join("config.json");
let raw = std::fs::read_to_string(&path)
.with_context(|| format!("failed to read {}", path.display()))?;
let mut stripped = json_comments::StripComments::new(raw.as_bytes());
let raw: RawUnifiedConfig = serde_json::from_reader(&mut stripped)
.with_context(|| format!("failed to parse {}", path.display()))?;
let is_tun = raw.tun.as_ref().and_then(|t| t.enable).unwrap_or(false);
let server = raw.server.unwrap_or_else(|| "127.0.0.1:50000".to_string());
let key = raw.access_key.unwrap_or_default();
let mtu = raw.mtu.unwrap_or(default_mtu());
let socks5 = raw.socks5_bind.unwrap_or_else(|| "127.0.0.1:1088".to_string());
let exclusions = raw.exclude.unwrap_or(RawExcludeSection {
domains: None,
ips: None,
processes: None,
});
let mux = raw.mux.unwrap_or(RawMuxSection {
enabled: None,
sessions: None,
});
Ok(ClientConfig {
mode: if is_tun { "tun".to_string() } else { "proxy".to_string() },
debug: raw.debug.unwrap_or(false),
ostp: OstpConfig {
server_addr: server,
local_bind_addr: "0.0.0.0:0".to_string(),
access_key: key,
handshake_timeout_ms: 5000,
io_timeout_ms: 2500,
mtu,
keepalive_interval_sec: default_keepalive(),
},
local_proxy: LocalProxyConfig {
bind_addr: socks5,
connect_timeout_ms: 15000,
},
transport: TransportConfig {
mode: raw.transport.as_ref().and_then(|t| t.mode.clone()).unwrap_or_else(default_transport_mode),
stealth_sni: raw.transport.as_ref().and_then(|t| t.stealth_sni.clone()).unwrap_or_default(),
},
exclusions: ExclusionConfig {
domains: exclusions.domains.unwrap_or_default(),
ips: exclusions.ips.unwrap_or_default(),
processes: exclusions.processes.unwrap_or_default(),
},
multiplex: MultiplexConfig {
enabled: mux.enabled.unwrap_or(false),
sessions: mux.sessions.unwrap_or(1),
},
dns_server: raw.tun.as_ref().and_then(|t| t.dns.clone()),
tun_stack: raw.tun.as_ref().and_then(|t| t.stack.clone()).unwrap_or_else(|| "system".to_string()),
kill_switch: raw.tun.as_ref().and_then(|t| t.kill_switch).unwrap_or(false),
gui: raw.gui,
})
}
}