mirror of https://github.com/ospab/ostp.git
docs: Update config format to modular architecture v0.3.1
This commit is contained in:
parent
580faf659a
commit
8ed66f9553
|
|
@ -0,0 +1,102 @@
|
|||
# Миграция конфигурации OSTP v0.3.1
|
||||
|
||||
В версии OSTP 0.3.1 мы полностью переработали архитектуру конфигурации `config.json` для клиента. Старая монолитная структура (где все настройки были в корневом объекте) заменена на модульную систему на базе массивов `inbounds` (входящие соединения) и `outbounds` (исходящие соединения), аналогично Xray/V2Ray/Sing-box.
|
||||
|
||||
Это позволяет OSTP масштабироваться, поддерживать несколько прокси-серверов, несколько точек входа (SOCKS5, TUN) и сложную маршрутизацию (`routing`).
|
||||
|
||||
## Автоматическая миграция
|
||||
|
||||
В ядро `ostp` встроен автоматический мигратор. При запуске любой программы (cli, gui, flutter) ядро проверит ваш `config.json`.
|
||||
|
||||
Если в конфигурации отсутствует поле `"version": "0.3.1"`, OSTP **автоматически** конвертирует ваш старый конфиг в новый модульный формат и сохранит его на диск без потери данных.
|
||||
|
||||
### Что происходит при миграции:
|
||||
|
||||
1. **TUN и SOCKS5** -> преобразуются в массив `inbounds`.
|
||||
- Настройка `socks5_bind` становится входящим `local_proxy` (SOCKS).
|
||||
- Настройка `tun` становится входящим `tun`.
|
||||
2. **Сервер OSTP** -> переносится в массив `outbounds`.
|
||||
- Параметры `server`, `access_key`, `transport`, `mux` объединяются в `outbound` с типом `"ostp"`.
|
||||
3. **Split Tunneling (Exclude)** -> преобразуется в `routing` правила.
|
||||
- Старые `domains` и `ips` конвертируются в правила, направляющие трафик в `"direct"` outbound.
|
||||
- Все остальные запросы по умолчанию направляются в `"proxy"` outbound.
|
||||
4. **Поля `version`**
|
||||
- Добавляется поле `"version": "0.3.1"`, чтобы предотвратить повторную миграцию в будущем. Поле `_comment` было удалено.
|
||||
|
||||
## Пример изменения
|
||||
|
||||
### До 0.3.1 (Старый формат)
|
||||
```json
|
||||
{
|
||||
"mode": "client",
|
||||
"log_level": "info",
|
||||
"server": "1.2.3.4:50000",
|
||||
"access_key": "secret",
|
||||
"socks5_bind": "127.0.0.1:1088",
|
||||
"tun": {
|
||||
"enable": true
|
||||
},
|
||||
"exclude": {
|
||||
"domains": ["localhost"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### После 0.3.1 (Новый формат)
|
||||
```json
|
||||
{
|
||||
"mode": "client",
|
||||
"version": "0.3.1",
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"auto_route": true,
|
||||
"mtu": 1140
|
||||
},
|
||||
{
|
||||
"type": "local_proxy",
|
||||
"tag": "socks-in",
|
||||
"protocol": "socks",
|
||||
"listen": "127.0.0.1",
|
||||
"port": 1088
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "ostp",
|
||||
"tag": "proxy",
|
||||
"server": "1.2.3.4",
|
||||
"port": 50000,
|
||||
"access_key": "secret",
|
||||
"transport": {
|
||||
"type": "udp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"routing": {
|
||||
"rules": [
|
||||
{
|
||||
"domain_suffix": ["localhost"],
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"default_outbound": "proxy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Информация для разработчиков GUI (ostp-gui, ostp-flutter)
|
||||
|
||||
Если вы разрабатываете интеграции или сторонние клиенты, **вам больше не нужно парсить старые поля**. Вы должны использовать массивы `inbounds` и `outbounds`. Если GUI передает `serde_json::Value` в ядро, ядро само проведет миграцию перед запуском. Однако для сохранения изменений из UI вы должны изменять именно новую структуру массивов.
|
||||
31
README.md
31
README.md
|
|
@ -118,14 +118,35 @@ graph TD
|
|||
```jsonc
|
||||
{
|
||||
"mode": "client",
|
||||
"server": "YOUR_SERVER_IP:50000",
|
||||
"access_key": "YOUR_SECRET_KEY",
|
||||
"socks5_bind": "127.0.0.1:1088",
|
||||
"transport": { "mode": "udp", "stealth_sni": "vk.com" },
|
||||
"tun": { "enable": false, "dns": "1.1.1.1" }
|
||||
"version": "0.3.1",
|
||||
"log": { "level": "info" },
|
||||
"inbounds": [
|
||||
{ "type": "local_proxy", "tag": "socks-in", "protocol": "socks", "listen": "127.0.0.1", "port": 1088 },
|
||||
{ "type": "tun", "tag": "tun-in", "auto_route": false, "mtu": 1140 }
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "ostp",
|
||||
"tag": "proxy",
|
||||
"server": "YOUR_SERVER_IP",
|
||||
"port": 50000,
|
||||
"access_key": "YOUR_SECRET_KEY",
|
||||
"transport": { "type": "udp" }
|
||||
},
|
||||
{ "type": "direct", "tag": "direct" },
|
||||
{ "type": "block", "tag": "block" }
|
||||
],
|
||||
"routing": {
|
||||
"rules": [
|
||||
{ "domain_suffix": ["localhost"], "outbound": "direct" }
|
||||
],
|
||||
"default_outbound": "proxy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** Upgrading from v0.2.x? Read the [v0.3.1 Migration Guide](MIGRATION_V0_3_1.md).
|
||||
|
||||
### 3. Run
|
||||
|
||||
```bash
|
||||
|
|
|
|||
57
README.ru.md
57
README.ru.md
|
|
@ -110,40 +110,37 @@ irm https://raw.githubusercontent.com/ospab/ostp/master/scripts/install.ps1 | ie
|
|||
```jsonc
|
||||
{
|
||||
"mode": "client",
|
||||
"server": "IP_СЕРВЕРА:50000",
|
||||
"access_key": "ВАШ_КЛЮЧ",
|
||||
"socks5_bind": "127.0.0.1:1088",
|
||||
"debug": false,
|
||||
// Настройки транспорта (udp или uot)
|
||||
"transport": {
|
||||
"mode": "udp",
|
||||
"stealth_sni": "vk.com"
|
||||
},
|
||||
// TUN-режим (полносистемный VPN)
|
||||
"tun": {
|
||||
"enable": false,
|
||||
"dns": "1.1.1.1"
|
||||
},
|
||||
// Мультиплексирование: несколько UDP-сессий
|
||||
"mux": {
|
||||
"enabled": false,
|
||||
"sessions": 2
|
||||
},
|
||||
// TURN-реле для заблокированных сетей
|
||||
"turn": {
|
||||
"enabled": false,
|
||||
"server_addr": "turn.example.com:3478",
|
||||
"username": "user",
|
||||
"access_key": "pass"
|
||||
},
|
||||
// Исключения (идут напрямую, минуя туннель)
|
||||
"exclude": {
|
||||
"domains": ["example.local"],
|
||||
"ips": ["192.168.0.0/16"]
|
||||
"version": "0.3.1",
|
||||
"log": { "level": "info" },
|
||||
"inbounds": [
|
||||
{ "type": "local_proxy", "tag": "socks-in", "protocol": "socks", "listen": "127.0.0.1", "port": 1088 },
|
||||
{ "type": "tun", "tag": "tun-in", "auto_route": false, "mtu": 1140 }
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "ostp",
|
||||
"tag": "proxy",
|
||||
"server": "IP_СЕРВЕРА",
|
||||
"port": 50000,
|
||||
"access_key": "ВАШ_КЛЮЧ",
|
||||
"transport": { "type": "udp" },
|
||||
"multiplex": { "enabled": false, "sessions": 1 }
|
||||
},
|
||||
{ "type": "direct", "tag": "direct" },
|
||||
{ "type": "block", "tag": "block" }
|
||||
],
|
||||
"routing": {
|
||||
"rules": [
|
||||
{ "domain_suffix": ["example.local"], "outbound": "direct" },
|
||||
{ "ip_cidr": ["192.168.0.0/16"], "outbound": "direct" }
|
||||
],
|
||||
"default_outbound": "proxy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Примечание:** Обновляетесь с v0.2.x? Прочтите [Гайд по миграции на v0.3.1](MIGRATION_V0_3_1.md).
|
||||
|
||||
---
|
||||
|
||||
## Использование
|
||||
|
|
|
|||
|
|
@ -7,6 +7,17 @@ pub struct BridgeMetrics {
|
|||
pub rtt_ms: AtomicU32,
|
||||
}
|
||||
|
||||
impl Default for BridgeMetrics {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bytes_sent: portable_atomic::AtomicU64::new(0),
|
||||
bytes_recv: portable_atomic::AtomicU64::new(0),
|
||||
connection_state: portable_atomic::AtomicU8::new(0),
|
||||
rtt_ms: portable_atomic::AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_socket_protector<F>(f: F)
|
||||
where
|
||||
F: Fn(i32) -> bool + Send + Sync + 'static,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClientConfig {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
#[serde(default)]
|
||||
pub log: LogConfig,
|
||||
#[serde(default)]
|
||||
|
|
@ -153,9 +155,150 @@ impl ClientConfig {
|
|||
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 config: ClientConfig = serde_json::from_reader(&mut stripped)
|
||||
.with_context(|| format!("failed to parse {}", path.display()))?;
|
||||
let raw_json: serde_json::Value = serde_json::from_reader(&mut stripped)
|
||||
.with_context(|| format!("failed to parse JSON from {}", path.display()))?;
|
||||
|
||||
let (migrated_json, was_migrated) = Self::migrate_json(raw_json);
|
||||
|
||||
if was_migrated {
|
||||
tracing::info!("Config was migrated to v0.3.1. Saving to {}", path.display());
|
||||
let serialized = serde_json::to_string_pretty(&migrated_json)?;
|
||||
std::fs::write(&path, serialized)
|
||||
.with_context(|| format!("failed to save migrated config to {}", path.display()))?;
|
||||
}
|
||||
|
||||
let config: ClientConfig = serde_json::from_value(migrated_json)
|
||||
.with_context(|| format!("failed to deserialize migrated config from {}", path.display()))?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Migrates old monolithic JSON to the new modular format.
|
||||
/// Returns the migrated JSON value and a boolean indicating if a migration occurred.
|
||||
pub fn migrate_json(mut json: serde_json::Value) -> (serde_json::Value, bool) {
|
||||
let is_migrated = json.get("version").and_then(|v| v.as_str()) == Some("0.3.1");
|
||||
if is_migrated {
|
||||
return (json, false);
|
||||
}
|
||||
|
||||
// Needs migration
|
||||
let mut new_json = serde_json::json!({
|
||||
"version": "0.3.1",
|
||||
});
|
||||
|
||||
// 1. Log level
|
||||
let log_level = if let Some(ll) = json.get("log_level") {
|
||||
ll.clone()
|
||||
} else if let Some(d) = json.get("debug") {
|
||||
if d.as_bool().unwrap_or(false) { serde_json::json!("debug") } else { serde_json::json!("info") }
|
||||
} else {
|
||||
serde_json::json!("info")
|
||||
};
|
||||
new_json["log"] = serde_json::json!({ "level": log_level });
|
||||
|
||||
// 2. Inbounds
|
||||
let mut inbounds = Vec::new();
|
||||
|
||||
if let Some(tun) = json.get("tun") {
|
||||
if tun.get("enable").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||
inbounds.push(serde_json::json!({
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"auto_route": true,
|
||||
"mtu": 1140
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let socks_bind = json.get("socks5_bind").and_then(|v| v.as_str()).unwrap_or("127.0.0.1:1088");
|
||||
let parts: Vec<&str> = socks_bind.split(':').collect();
|
||||
let listen = parts.get(0).unwrap_or(&"127.0.0.1");
|
||||
let port = parts.get(1).unwrap_or(&"1088").parse::<u16>().unwrap_or(1088);
|
||||
|
||||
inbounds.push(serde_json::json!({
|
||||
"type": "local_proxy",
|
||||
"tag": "socks-in",
|
||||
"protocol": "socks",
|
||||
"listen": listen,
|
||||
"port": port
|
||||
}));
|
||||
|
||||
new_json["inbounds"] = serde_json::Value::Array(inbounds);
|
||||
|
||||
// 3. Outbounds
|
||||
let mut outbounds = Vec::new();
|
||||
let server_full = json.get("server").and_then(|v| v.as_str()).unwrap_or("127.0.0.1:50000");
|
||||
let server_parts: Vec<&str> = server_full.split(':').collect();
|
||||
let server_host = server_parts.get(0).unwrap_or(&"127.0.0.1");
|
||||
let server_port = server_parts.get(1).unwrap_or(&"50000").parse::<u16>().unwrap_or(50000);
|
||||
let access_key = json.get("access_key").and_then(|v| v.as_str()).unwrap_or("");
|
||||
|
||||
let transport_type = json.get("transport").and_then(|t| t.get("mode").or(t.get("type"))).and_then(|v| v.as_str()).unwrap_or("udp");
|
||||
let mux_enabled = json.get("mux").and_then(|m| m.get("enabled")).and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
let mux_sessions = json.get("mux").and_then(|m| m.get("sessions")).and_then(|v| v.as_u64()).unwrap_or(1);
|
||||
|
||||
outbounds.push(serde_json::json!({
|
||||
"type": "ostp",
|
||||
"tag": "proxy",
|
||||
"server": server_host,
|
||||
"port": server_port,
|
||||
"access_key": access_key,
|
||||
"transport": {
|
||||
"type": transport_type
|
||||
},
|
||||
"multiplex": {
|
||||
"enabled": mux_enabled,
|
||||
"sessions": mux_sessions
|
||||
}
|
||||
}));
|
||||
|
||||
outbounds.push(serde_json::json!({
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
}));
|
||||
|
||||
outbounds.push(serde_json::json!({
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}));
|
||||
|
||||
new_json["outbounds"] = serde_json::Value::Array(outbounds);
|
||||
|
||||
// 4. Routing
|
||||
let mut rules = Vec::new();
|
||||
|
||||
// Migrate exclusions to route to direct
|
||||
if let Some(exclude) = json.get("exclude") {
|
||||
if let Some(domains) = exclude.get("domains") {
|
||||
rules.push(serde_json::json!({
|
||||
"domain_suffix": domains,
|
||||
"outbound": "direct"
|
||||
}));
|
||||
}
|
||||
if let Some(ips) = exclude.get("ips") {
|
||||
rules.push(serde_json::json!({
|
||||
"ip_cidr": ips,
|
||||
"outbound": "direct"
|
||||
}));
|
||||
}
|
||||
if let Some(processes) = exclude.get("processes") {
|
||||
rules.push(serde_json::json!({
|
||||
"process_name": processes,
|
||||
"outbound": "direct"
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
new_json["routing"] = serde_json::json!({
|
||||
"rules": rules,
|
||||
"default_outbound": "proxy"
|
||||
});
|
||||
|
||||
// 5. Preserve GUI state
|
||||
if let Some(gui) = json.get("gui") {
|
||||
new_json["gui"] = gui.clone();
|
||||
}
|
||||
|
||||
(new_json, true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
378
ostp/src/main.rs
378
ostp/src/main.rs
|
|
@ -65,7 +65,7 @@ struct Args {
|
|||
proxy_env_clear: bool,
|
||||
}
|
||||
|
||||
fn parse_ostp_link(link: &str) -> Result<ClientConfig> {
|
||||
fn parse_ostp_link(link: &str) -> Result<serde_json::Value> {
|
||||
let parsed = url::Url::parse(link)
|
||||
.map_err(|e| anyhow!("Failed to parse share link URL: {e}"))?;
|
||||
|
||||
|
|
@ -98,29 +98,55 @@ fn parse_ostp_link(link: &str) -> Result<ClientConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(ClientConfig {
|
||||
server,
|
||||
access_key,
|
||||
mtu: None,
|
||||
transport: Some(TransportConfigRaw {
|
||||
mode: Some(transport_mode),
|
||||
stealth_sni: Some(sni.clone()),
|
||||
wss: Some(wss_enabled),
|
||||
}),
|
||||
socks5_bind: Some("127.0.0.1:1088".to_string()),
|
||||
tun: Some(TunConfig {
|
||||
enable: tun_enabled,
|
||||
wintun_path: Some("./wintun.dll".to_string()),
|
||||
ipv4_address: Some("10.1.0.2/24".to_string()),
|
||||
dns: tun_dns,
|
||||
kill_switch: Some(false),
|
||||
}),
|
||||
|
||||
debug: Some(false),
|
||||
exclude: None,
|
||||
mux: None,
|
||||
gui: None,
|
||||
})
|
||||
Ok(serde_json::json!({
|
||||
"version": "0.3.1",
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"auto_route": tun_enabled,
|
||||
"mtu": 1140
|
||||
},
|
||||
{
|
||||
"type": "local_proxy",
|
||||
"tag": "socks-in",
|
||||
"protocol": "socks",
|
||||
"listen": "127.0.0.1",
|
||||
"port": 1088
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "ostp",
|
||||
"tag": "proxy",
|
||||
"server": parsed.host_str().unwrap_or(""),
|
||||
"port": parsed.port().unwrap_or(50000),
|
||||
"access_key": access_key,
|
||||
"transport": {
|
||||
"type": transport_mode
|
||||
},
|
||||
"multiplex": {
|
||||
"enabled": false,
|
||||
"sessions": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"routing": {
|
||||
"rules": [],
|
||||
"default_outbound": "proxy"
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn generate_secure_key(format_type: &str) -> String {
|
||||
|
|
@ -148,7 +174,7 @@ fn parse_outbound_action(value: Option<String>) -> ostp_server::OutboundAction {
|
|||
#[serde(tag = "mode", rename_all = "lowercase")]
|
||||
enum AppMode {
|
||||
Server(ServerConfig),
|
||||
Client(ClientConfig),
|
||||
Client(serde_json::Value),
|
||||
Relay(RelayServerConfig),
|
||||
}
|
||||
|
||||
|
|
@ -178,8 +204,11 @@ impl UnifiedConfig {
|
|||
}
|
||||
}
|
||||
AppMode::Client(cfg) => {
|
||||
if cfg.access_key.is_empty() {
|
||||
anyhow::bail!("Client configuration must contain an access_key.");
|
||||
if let Some(outbounds) = cfg.get("outbounds").and_then(|o| o.as_array()) {
|
||||
let has_proxy = outbounds.iter().any(|o| o.get("type").and_then(|t| t.as_str()) == Some("ostp"));
|
||||
if !has_proxy {
|
||||
anyhow::bail!("Client configuration must contain an ostp outbound proxy.");
|
||||
}
|
||||
}
|
||||
}
|
||||
AppMode::Relay(cfg) => {
|
||||
|
|
@ -222,11 +251,37 @@ impl UserConfig {
|
|||
pub fn limit(&self) -> Option<u64> {
|
||||
match self {
|
||||
UserConfig::KeyOnly(_) => None,
|
||||
UserConfig::Detailed { limit_bytes, .. } => limit_bytes.clone(),
|
||||
UserConfig::Detailed { limit_bytes, .. } => *limit_bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct OutboundConfig {
|
||||
enabled: bool,
|
||||
protocol: String,
|
||||
address: String,
|
||||
port: u16,
|
||||
#[serde(default)]
|
||||
rules: Vec<OutboundRule>,
|
||||
default_action: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct OutboundRule {
|
||||
domain_suffix: Option<Vec<String>>,
|
||||
ip_cidr: Option<Vec<String>>,
|
||||
protocol: Option<String>,
|
||||
action: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
struct TransportConfigRaw {
|
||||
mode: Option<String>,
|
||||
stealth_sni: Option<String>,
|
||||
wss: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct ServerConfig {
|
||||
listen: ListenConfig,
|
||||
|
|
@ -302,68 +357,7 @@ struct FallbackCfg {
|
|||
target: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct ClientConfig {
|
||||
server: String,
|
||||
access_key: String,
|
||||
mtu: Option<usize>,
|
||||
socks5_bind: Option<String>,
|
||||
tun: Option<TunConfig>,
|
||||
debug: Option<bool>,
|
||||
exclude: Option<ExcludeConfig>,
|
||||
mux: Option<MuxConfig>,
|
||||
transport: Option<TransportConfigRaw>,
|
||||
gui: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
struct TransportConfigRaw {
|
||||
mode: Option<String>,
|
||||
stealth_sni: Option<String>,
|
||||
wss: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
struct TunConfig {
|
||||
enable: bool,
|
||||
wintun_path: Option<String>,
|
||||
ipv4_address: Option<String>,
|
||||
dns: Option<String>,
|
||||
kill_switch: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct OutboundConfig {
|
||||
enabled: bool,
|
||||
protocol: String,
|
||||
address: String,
|
||||
port: u16,
|
||||
#[serde(default)]
|
||||
rules: Vec<OutboundRule>,
|
||||
default_action: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct OutboundRule {
|
||||
domain_suffix: Option<Vec<String>>,
|
||||
ip_cidr: Option<Vec<String>>,
|
||||
protocol: Option<String>,
|
||||
action: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct ExcludeConfig {
|
||||
domains: Option<Vec<String>>,
|
||||
ips: Option<Vec<String>>,
|
||||
processes: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct MuxConfig {
|
||||
enabled: Option<bool>,
|
||||
sessions: Option<usize>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
|
|
@ -679,34 +673,68 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> {
|
|||
let _ = key_for_gen;
|
||||
let _ = &sni;
|
||||
|
||||
let server_parts: Vec<&str> = server.split(':').collect();
|
||||
let server_host = server_parts.get(0).unwrap_or(&"127.0.0.1");
|
||||
let server_port = server_parts.get(1).unwrap_or(&"50000").parse::<u16>().unwrap_or(50000);
|
||||
|
||||
let socks_parts: Vec<&str> = socks_bind.split(':').collect();
|
||||
let socks_host = socks_parts.get(0).unwrap_or(&"127.0.0.1");
|
||||
let socks_port = socks_parts.get(1).unwrap_or(&"1088").parse::<u16>().unwrap_or(1088);
|
||||
|
||||
let client_json = serde_json::json!({
|
||||
"mode": "client",
|
||||
"log_level": "info",
|
||||
"server": server,
|
||||
"access_key": access_key,
|
||||
"socks5_bind": socks_bind,
|
||||
"tun": {
|
||||
"enable": tun_enable,
|
||||
"wintun_path": "./wintun.dll",
|
||||
"ipv4_address": "10.1.0.2/24",
|
||||
"dns": tun_dns,
|
||||
"kill_switch": kill_switch
|
||||
"version": "0.3.1",
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"exclude": {
|
||||
"domains": ["localhost", "127.0.0.1"],
|
||||
"ips": [],
|
||||
"processes": []
|
||||
},
|
||||
"transport": {
|
||||
"mode": transport_mode,
|
||||
"stealth_sni": "www.microsoft.com",
|
||||
"wss": false
|
||||
},
|
||||
"mux": {
|
||||
"enabled": mux_enable,
|
||||
"sessions": mux_sessions
|
||||
},
|
||||
"debug": false
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"auto_route": tun_enable,
|
||||
"mtu": 1140
|
||||
},
|
||||
{
|
||||
"type": "local_proxy",
|
||||
"tag": "socks-in",
|
||||
"protocol": "socks",
|
||||
"listen": socks_host,
|
||||
"port": socks_port
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "ostp",
|
||||
"tag": "proxy",
|
||||
"server": server_host,
|
||||
"port": server_port,
|
||||
"access_key": access_key,
|
||||
"transport": {
|
||||
"type": transport_mode
|
||||
},
|
||||
"multiplex": {
|
||||
"enabled": mux_enable,
|
||||
"sessions": mux_sessions
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"routing": {
|
||||
"rules": [
|
||||
{
|
||||
"domain_suffix": ["localhost", "127.0.0.1"],
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"default_outbound": "proxy"
|
||||
}
|
||||
});
|
||||
|
||||
let actual_path = wizard_save_config(config_path, &client_json)?;
|
||||
|
|
@ -755,6 +783,7 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> {
|
|||
// intentional: step text then daemon call below
|
||||
let server_json = serde_json::json!({
|
||||
"mode": "server",
|
||||
"version": "0.3.1",
|
||||
"log_level": "info",
|
||||
"listen": listen,
|
||||
"access_keys": access_keys,
|
||||
|
|
@ -867,6 +896,7 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> {
|
|||
let panel_bind = format!("0.0.0.0:{}", panel_port);
|
||||
let server_json = serde_json::json!({
|
||||
"mode": "server",
|
||||
"version": "0.3.1",
|
||||
"log_level": "info",
|
||||
"listen": listen,
|
||||
"access_keys": access_keys,
|
||||
|
|
@ -929,6 +959,7 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> {
|
|||
wizard_step(2, TOTAL, "Saving configuration");
|
||||
let relay_json = serde_json::json!({
|
||||
"mode": "relay",
|
||||
"version": "0.3.1",
|
||||
"listen": listen,
|
||||
"upstream_tcp": upstream,
|
||||
"upstream_udp": upstream,
|
||||
|
|
@ -1060,9 +1091,15 @@ async fn run_app() -> Result<()> {
|
|||
let mut stripped = json_comments::StripComments::new(content.as_bytes());
|
||||
if let Ok(config) = serde_json::from_reader::<_, UnifiedConfig>(&mut stripped) {
|
||||
if let AppMode::Client(c) = config.mode {
|
||||
if let Some(bind) = c.socks5_bind {
|
||||
if let Some(p) = bind.split(':').last().and_then(|s| s.parse::<u16>().ok()) {
|
||||
port = p;
|
||||
let (migrated, _) = ostp_client::config::ClientConfig::migrate_json(c);
|
||||
if let Some(inbounds) = migrated.get("inbounds").and_then(|i| i.as_array()) {
|
||||
for inbound in inbounds {
|
||||
if inbound.get("type").and_then(|t| t.as_str()) == Some("local_proxy") {
|
||||
if let Some(p) = inbound.get("port").and_then(|p| p.as_u64()) {
|
||||
port = p as u16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1150,8 +1187,14 @@ async fn run_app() -> Result<()> {
|
|||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
if input.trim().eq_ignore_ascii_case("y") {
|
||||
if let Some(tun) = &mut client_cfg.tun {
|
||||
tun.enable = true;
|
||||
if let Some(i_val) = client_cfg.get_mut("inbounds") {
|
||||
if let Some(inbounds) = i_val.as_array_mut() {
|
||||
for inbound in inbounds.iter_mut() {
|
||||
if inbound.get("type").and_then(|t| t.as_str()) == Some("tun") {
|
||||
inbound["auto_route"] = serde_json::json!(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1170,14 +1213,17 @@ async fn run_app() -> Result<()> {
|
|||
sessions = s;
|
||||
}
|
||||
}
|
||||
if client_cfg.mux.is_none() {
|
||||
client_cfg.mux = Some(MuxConfig {
|
||||
enabled: Some(true),
|
||||
sessions: Some(sessions),
|
||||
});
|
||||
} else if let Some(mux) = &mut client_cfg.mux {
|
||||
mux.enabled = Some(true);
|
||||
mux.sessions = Some(sessions);
|
||||
if let Some(o_val) = client_cfg.get_mut("outbounds") {
|
||||
if let Some(outbounds) = o_val.as_array_mut() {
|
||||
for outbound in outbounds.iter_mut() {
|
||||
if outbound.get("type").and_then(|t| t.as_str()) == Some("ostp") {
|
||||
outbound["multiplex"] = serde_json::json!({
|
||||
"enabled": true,
|
||||
"sessions": sessions
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1186,7 +1232,7 @@ async fn run_app() -> Result<()> {
|
|||
input.clear();
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
if input.trim().eq_ignore_ascii_case("y") {
|
||||
client_cfg.debug = Some(true);
|
||||
client_cfg["log"]["level"] = serde_json::json!("debug");
|
||||
}
|
||||
|
||||
return run_client_directly(client_cfg).await;
|
||||
|
|
@ -1226,8 +1272,24 @@ async fn run_app() -> Result<()> {
|
|||
}
|
||||
AppMode::Client(c) => {
|
||||
println!("{} Config OK: client mode", "[ostp]".green().bold());
|
||||
println!(" Server: {}", c.server.cyan());
|
||||
println!(" Key: {}...", &c.access_key[..8.min(c.access_key.len())].yellow());
|
||||
let (migrated, _) = ostp_client::config::ClientConfig::migrate_json(c.clone());
|
||||
let mut display_server = "unknown";
|
||||
let mut display_key = "unknown";
|
||||
if let Some(outbounds) = migrated.get("outbounds").and_then(|o| o.as_array()) {
|
||||
for outbound in outbounds {
|
||||
if outbound.get("type").and_then(|t| t.as_str()) == Some("ostp") {
|
||||
if let Some(s) = outbound.get("server").and_then(|s| s.as_str()) {
|
||||
display_server = s;
|
||||
}
|
||||
if let Some(k) = outbound.get("access_key").and_then(|k| k.as_str()) {
|
||||
display_key = k;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(" Server: {}", display_server.cyan());
|
||||
println!(" Key: {}...", &display_key[..8.min(display_key.len())].yellow());
|
||||
}
|
||||
AppMode::Relay(r) => {
|
||||
println!("{} Config OK: relay mode", "[ostp]".green().bold());
|
||||
|
|
@ -1601,46 +1663,26 @@ fn cmd_update() -> Result<()> {
|
|||
anyhow::bail!("The 'update' command is only supported on Linux/Unix systems.");
|
||||
}
|
||||
|
||||
async fn run_client_directly(client_cfg: ClientConfig) -> Result<()> {
|
||||
let is_tun_enabled = client_cfg.tun.as_ref().map(|t| t.enable).unwrap_or(false);
|
||||
async fn run_client_directly(client_cfg: serde_json::Value) -> Result<()> {
|
||||
let (migrated, _) = ostp_client::config::ClientConfig::migrate_json(client_cfg);
|
||||
let client_conf: ostp_client::config::ClientConfig = serde_json::from_value(migrated)?;
|
||||
|
||||
let mut is_tun_enabled = false;
|
||||
for inbound in &client_conf.inbounds {
|
||||
if matches!(inbound, ostp_client::config::InboundConfig::Tun { .. }) {
|
||||
is_tun_enabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mode_str = if is_tun_enabled { "tun" } else { "proxy" };
|
||||
println!("{} Starting client (mode={}, server={})", "[ostp]".cyan().bold(), mode_str.yellow(), client_cfg.server.cyan()); let client_conf = ostp_client::config::ClientConfig {
|
||||
mode: if is_tun_enabled { "tun".to_string() } else { "proxy".to_string() },
|
||||
tun_stack: "native".to_string(),
|
||||
debug: client_cfg.debug.unwrap_or(false),
|
||||
ostp: ostp_client::config::OstpConfig {
|
||||
server_addr: client_cfg.server.clone(),
|
||||
local_bind_addr: "0.0.0.0:0".to_string(),
|
||||
access_key: client_cfg.access_key.clone(),
|
||||
handshake_timeout_ms: 5000,
|
||||
io_timeout_ms: 2500,
|
||||
mtu: client_cfg.mtu.unwrap_or(1350),
|
||||
keepalive_interval_sec: 5,
|
||||
},
|
||||
local_proxy: ostp_client::config::LocalProxyConfig {
|
||||
bind_addr: client_cfg.socks5_bind.clone().unwrap_or_else(|| "127.0.0.1:1088".to_string()),
|
||||
connect_timeout_ms: 5000,
|
||||
},
|
||||
exclusions: ostp_client::config::ExclusionConfig {
|
||||
domains: client_cfg.exclude.as_ref().and_then(|e| e.domains.clone()).unwrap_or_default(),
|
||||
ips: client_cfg.exclude.as_ref().and_then(|e| e.ips.clone()).unwrap_or_default(),
|
||||
processes: client_cfg.exclude.as_ref().and_then(|e| e.processes.clone()).unwrap_or_default(),
|
||||
},
|
||||
multiplex: ostp_client::config::MultiplexConfig {
|
||||
enabled: client_cfg.mux.as_ref().and_then(|m| m.enabled).unwrap_or(false),
|
||||
sessions: client_cfg.mux.as_ref().and_then(|m| m.sessions).unwrap_or(1),
|
||||
},
|
||||
transport: ostp_client::config::TransportConfig {
|
||||
mode: client_cfg.transport.as_ref().and_then(|t| t.mode.clone()).unwrap_or_else(|| "udp".to_string()),
|
||||
stealth_sni: client_cfg.transport.as_ref().and_then(|t| t.stealth_sni.clone()).unwrap_or_else(|| "microsoft.com".to_string()),
|
||||
wss: client_cfg.transport.as_ref().and_then(|t| t.wss).unwrap_or(false),
|
||||
},
|
||||
dns_server: client_cfg.tun.as_ref().and_then(|t| t.dns.clone()),
|
||||
kill_switch: client_cfg.tun.as_ref().and_then(|t| t.kill_switch).unwrap_or(false),
|
||||
gui: None,
|
||||
};
|
||||
println!("{} Starting client (mode={})", "[ostp]".cyan().bold(), mode_str.yellow());
|
||||
|
||||
// Run the client implementation
|
||||
ostp_client::runner::run_client(client_conf).await?;
|
||||
let (_shutdown_tx, rx) = tokio::sync::watch::channel(false);
|
||||
let metrics = std::sync::Arc::new(ostp_client::bridge::BridgeMetrics::default());
|
||||
|
||||
// Launch the core runner directly.
|
||||
ostp_client::runner::run_client_core(client_conf, metrics, rx, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue