diff --git a/Cargo.lock b/Cargo.lock index b637bff..fe8a5bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1497,9 +1497,11 @@ dependencies = [ "hkdf", "hmac", "rand 0.8.5", + "serde", "sha2", "snow", "thiserror 1.0.69", + "tokio", "tracing", "x25519-dalek", ] diff --git a/fix_delimiter.py b/fix_delimiter.py deleted file mode 100644 index 0ceda5f..0000000 --- a/fix_delimiter.py +++ /dev/null @@ -1,36 +0,0 @@ -import os - -with open('ostp/src/main.rs', 'r', encoding='utf-8') as f: - content = f.read() - -old_block = ''' if let Some(key) = first_key { - let host = get_or_ask_public_ip(&args.config); - let mut query_params = Vec::::new(); - query_params.push("type=udp".to_string()); - - let mut link = format!("ostp://{}@{}:{}", key, host, port); - if !query_params.is_empty() { - link.push('?'); - link.push_str(&query_params.join("&")); - } - println!(" [1] {}", link); - }''' - -new_block = ''' if let Some(key) = first_key { - let host = get_or_ask_public_ip(&args.config); - let mut query_params = Vec::::new(); - query_params.push("type=udp".to_string()); - - let mut link = format!("ostp://{}@{}:{}", key, host, port); - if !query_params.is_empty() { - link.push('?'); - link.push_str(&query_params.join("&")); - } - println!(" [1] {}", link); - } - }''' - -content = content.replace(old_block, new_block) - -with open('ostp/src/main.rs', 'w', encoding='utf-8') as f: - f.write(content) diff --git a/icons/logo.svg b/icons/logo.svg index 9cef1fb..237b566 100644 --- a/icons/logo.svg +++ b/icons/logo.svg @@ -1,6 +1,5 @@ - diff --git a/icons/logo_icon.svg b/icons/logo_icon.svg new file mode 100644 index 0000000..82296cd --- /dev/null +++ b/icons/logo_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ostp-client/src/bridge.rs b/ostp-client/src/bridge.rs index 1a49bd0..992272c 100644 --- a/ostp-client/src/bridge.rs +++ b/ostp-client/src/bridge.rs @@ -18,7 +18,7 @@ impl Default for BridgeMetrics { } } -pub fn set_socket_protector(f: F) +pub fn set_socket_protector(_f: F) where F: Fn(i32) -> bool + Send + Sync + 'static, { diff --git a/ostp-client/src/config.rs b/ostp-client/src/config.rs index e9866ac..aedbbff 100644 --- a/ostp-client/src/config.rs +++ b/ostp-client/src/config.rs @@ -190,15 +190,15 @@ impl ClientConfig { /// 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"); + pub fn migrate_json(json: serde_json::Value) -> (serde_json::Value, bool) { + let is_migrated = json.get("version").and_then(|v| v.as_str()) == Some(env!("CARGO_PKG_VERSION")); if is_migrated { return (json, false); } // Needs migration let mut new_json = serde_json::json!({ - "version": "0.3.1", + "version": env!("CARGO_PKG_VERSION"), }); // 1. Log level diff --git a/ostp-client/src/runner.rs b/ostp-client/src/runner.rs index fda5c28..1b2e833 100644 --- a/ostp-client/src/runner.rs +++ b/ostp-client/src/runner.rs @@ -1,8 +1,7 @@ use anyhow::Result; use std::sync::Arc; -use tokio::sync::{mpsc, watch}; +use tokio::sync::watch; -use crate::app::{BridgeCommand, ConnectionStatus, UiEvent}; use crate::config::{ClientConfig, InboundConfig}; use crate::tunnel::balancer::Balancer; use crate::tunnel::outbounds::OutboundManager; @@ -10,9 +9,9 @@ use crate::tunnel::router::Router; pub async fn run_client_core( config: ClientConfig, - metrics: Arc, + _metrics: Arc, mut shutdown_rx_ext: watch::Receiver, - config_rx: Option>, + _config_rx: Option>, ) -> Result<()> { println!("[ostp] Starting run_client_core with multi-server architecture"); diff --git a/ostp-client/src/transport/dns.rs b/ostp-client/src/transport/dns.rs index fd718ef..e148cc2 100644 --- a/ostp-client/src/transport/dns.rs +++ b/ostp-client/src/transport/dns.rs @@ -5,8 +5,9 @@ use tokio::net::UdpSocket; use tokio::sync::{mpsc, Mutex}; use rand::Rng; -use ostp_core::dns::{ - DnsPacket, DnsRecordType, encode_payload_to_domain, decode_domain_to_payload, +pub use ostp_core::dns::{ + DnsPacket, DnsRecordType, encode_payload_to_domain, + decode_domain_to_payload, }; use crate::transport::Transport; @@ -64,7 +65,7 @@ pub async fn start_dns_transport(domain: String, resolver: String, _pubkey: Opti }); // Receive task (reads from UDP socket, decodes DNS answer, sends to app) - let base_domain_rx = domain.clone(); + let _base_domain_rx = domain.clone(); tokio::spawn(async move { let mut buf = vec![0u8; 65535]; loop { diff --git a/ostp-client/src/tunnel/balancer.rs b/ostp-client/src/tunnel/balancer.rs index 9951f59..54e5232 100644 --- a/ostp-client/src/tunnel/balancer.rs +++ b/ostp-client/src/tunnel/balancer.rs @@ -1,6 +1,5 @@ use crate::config::{ClientConfig, OutboundConfig}; use std::collections::HashMap; -use std::sync::Arc; pub struct Balancer { outbounds: HashMap, diff --git a/ostp-client/src/tunnel/inbounds/local_proxy.rs b/ostp-client/src/tunnel/inbounds/local_proxy.rs index 92279ba..22a9c7b 100644 --- a/ostp-client/src/tunnel/inbounds/local_proxy.rs +++ b/ostp-client/src/tunnel/inbounds/local_proxy.rs @@ -85,7 +85,7 @@ async fn handle_socks5_connection( } let atyp = buf[3]; - let (target_host, mut ip_addr) = match atyp { + let (target_host, ip_addr) = match atyp { 0x01 => { // IPv4 stream.read_exact(&mut buf[0..4]).await?; let ip = std::net::Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3]); diff --git a/ostp-client/src/tunnel/inbounds/tun.rs b/ostp-client/src/tunnel/inbounds/tun.rs index 43c7c8e..6746545 100644 --- a/ostp-client/src/tunnel/inbounds/tun.rs +++ b/ostp-client/src/tunnel/inbounds/tun.rs @@ -13,7 +13,7 @@ pub async fn run_tun_inbound( outbound_manager: Arc, mut shutdown: watch::Receiver, ) -> Result<()> { - use std::net::ToSocketAddrs; + use netstack_smoltcp::StackBuilder; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use futures::{StreamExt, SinkExt}; @@ -72,7 +72,7 @@ pub async fn run_tun_inbound( #[allow(unused_variables)] let mut _route_guard = None; - let (mut tun_to_stack, mut stack_to_tun) = { + let (tun_to_stack, stack_to_tun) = { #[cfg(target_os = "android")] { if let Some(fd) = fd { @@ -183,7 +183,7 @@ pub async fn run_tun_inbound( let router_tcp = router.clone(); let tag_tcp = tag.clone(); - let mut tcp_accept_task = tokio::spawn(async move { + let tcp_accept_task = tokio::spawn(async move { let Some(mut listener) = tcp_listener else { return; }; while let Some((mut stream, local, remote)) = listener.next().await { let om = outbound_manager_tcp.clone(); @@ -249,7 +249,7 @@ pub async fn run_tun_inbound( let router_udp = router.clone(); let tag_udp = tag.clone(); - let mut udp_proxy_task = tokio::spawn(async move { + let udp_proxy_task = tokio::spawn(async move { if let Some(udp_sock) = udp_socket { let (mut udp_rx, _udp_tx) = udp_sock.split(); while let Some((payload, local, remote)) = udp_rx.next().await { diff --git a/ostp-client/src/tunnel/outbounds/mod.rs b/ostp-client/src/tunnel/outbounds/mod.rs index a9219cd..7e73516 100644 --- a/ostp-client/src/tunnel/outbounds/mod.rs +++ b/ostp-client/src/tunnel/outbounds/mod.rs @@ -1,6 +1,5 @@ use anyhow::{anyhow, Result}; use std::sync::Arc; -use tokio::net::TcpStream; use crate::tunnel::balancer::Balancer; use crate::config::OutboundConfig; diff --git a/ostp-client/src/tunnel/outbounds/ostp.rs b/ostp-client/src/tunnel/outbounds/ostp.rs index f5d979b..401ee48 100644 --- a/ostp-client/src/tunnel/outbounds/ostp.rs +++ b/ostp-client/src/tunnel/outbounds/ostp.rs @@ -1,10 +1,9 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use tokio::net::TcpStream; use crate::config::{TransportConfig, MultiplexConfig}; -use ostp_core::{NoiseRole, OstpEvent, ProtocolAction, ProtocolConfig, ProtocolMachine}; +use ostp_core::{OstpEvent, ProtocolAction, ProtocolConfig, ProtocolMachine}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::UdpSocket; pub async fn dial_tcp( server: &str, diff --git a/ostp-core/Cargo.toml b/ostp-core/Cargo.toml index b8b279a..c8cdd56 100644 --- a/ostp-core/Cargo.toml +++ b/ostp-core/Cargo.toml @@ -17,3 +17,5 @@ sha2.workspace = true hmac.workspace = true x25519-dalek = { version = "2.0.1", features = ["static_secrets"] } hkdf = "0.12.0" +tokio.workspace = true +serde = { version = "1.0", features = ["derive"] } diff --git a/ostp-core/src/dns_prober.rs b/ostp-core/src/dns_prober.rs new file mode 100644 index 0000000..b04f3c8 --- /dev/null +++ b/ostp-core/src/dns_prober.rs @@ -0,0 +1,94 @@ +use std::time::Duration; +use tokio::time::Instant; +use crate::dns::{DnsPacket, DnsRecordType, encode_payload_to_domain}; +use rand::Rng; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone)] +pub struct DnsProbeResult { + pub name: String, + pub ip: String, + pub latency_ms: Option, +} + +const PUBLIC_DNS_SERVERS: &[(&str, &str)] = &[ + ("Cloudflare", "1.1.1.1"), + ("Cloudflare2", "1.0.0.1"), + ("Google", "8.8.8.8"), + ("Google2", "8.8.4.4"), + ("Quad9", "9.9.9.9"), + ("AdGuard", "94.140.14.14"), + ("Yandex", "77.88.8.8"), + ("Yandex2", "77.88.8.1"), + ("SkyDNS", "193.58.251.251"), + ("AliDNS", "223.5.5.5"), + ("Tencent", "119.29.29.29"), + ("114DNS", "114.114.114.114"), + ("Shecan", "178.22.122.100"), + ("Electro", "78.157.42.100"), + ("Begzar", "185.55.226.26"), +]; + +async fn probe_resolver(domain: &str, resolver_ip: &str) -> Option { + let (probe_bytes, id) = { + let mut rng = rand::thread_rng(); + let probe_bytes: [u8; 4] = rng.gen(); + let id: u16 = rng.gen(); + (probe_bytes, id) + }; + + let fqdn = encode_payload_to_domain(&probe_bytes, domain); + let qtype = if rand::thread_rng().gen_bool(0.5) { DnsRecordType::TXT } else { DnsRecordType::NULL }; + let packet = DnsPacket::new_query(id, &fqdn, qtype); + let encoded = packet.encode(); + + let sock = tokio::net::UdpSocket::bind("0.0.0.0:0").await.ok()?; + sock.connect(format!("{}:53", resolver_ip)).await.ok()?; + + let start = Instant::now(); + sock.send(&encoded).await.ok()?; + + let mut buf = [0u8; 4096]; + match tokio::time::timeout(Duration::from_secs(2), sock.recv(&mut buf)).await { + Ok(Ok(n)) => { + if let Some(resp) = DnsPacket::decode(&buf[..n]) { + // Check if RCODE == 0 (NOERROR) and it has answers + let rcode = resp.flags & 0x000F; + if rcode == 0 && !resp.answers.is_empty() { + return Some(start.elapsed().as_millis() as u64); + } + } + None + }, + _ => None, + } +} + +pub async fn run_dns_prober(domain: &str) -> Result, String> { + if domain.is_empty() { + return Err("Please enter the tunnel domain first (e.g. tunnel.myvpn.com)".into()); + } + + let tasks: Vec<_> = PUBLIC_DNS_SERVERS + .iter() + .map(|(name, ip)| { + let domain = domain.to_string(); + let name = name.to_string(); + let ip = ip.to_string(); + tokio::spawn(async move { + let latency_ms = probe_resolver(&domain, &ip).await; + DnsProbeResult { name, ip, latency_ms } + }) + }) + .collect(); + + let mut results = Vec::with_capacity(tasks.len()); + for task in tasks { + if let Ok(r) = task.await { + results.push(r); + } + } + + results.sort_by_key(|r| r.latency_ms.unwrap_or(u64::MAX)); + Ok(results) +} diff --git a/ostp-core/src/lib.rs b/ostp-core/src/lib.rs index 335d3bc..4d52f5d 100644 --- a/ostp-core/src/lib.rs +++ b/ostp-core/src/lib.rs @@ -5,6 +5,7 @@ pub mod protocol; pub mod relay; pub mod resumption; pub mod dns; +pub mod dns_prober; pub use crypto::NoiseRole; pub use framing::{TrafficProfile, PaddingStrategy}; diff --git a/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/MainActivity.kt b/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/MainActivity.kt index 1f9dd8d..1d912cd 100644 --- a/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/MainActivity.kt +++ b/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/MainActivity.kt @@ -95,6 +95,15 @@ class MainActivity : FlutterActivity() { result.error("ERROR", e.message, null) } } + "runDnsProber" -> { + try { + val domain = call.argument("domain") ?: "example.com" + val json = net.ostp.client.OstpClientSdk.nativeRunDnsProber(domain) + result.success(json) + } catch (e: Throwable) { + result.error("ERROR", e.message, null) + } + } "getInstalledApps" -> { try { val pm = packageManager diff --git a/ostp-flutter/android/app/src/main/kotlin/net/ostp/client/OstpClientSdk.kt b/ostp-flutter/android/app/src/main/kotlin/net/ostp/client/OstpClientSdk.kt index a05634b..291e0df 100644 --- a/ostp-flutter/android/app/src/main/kotlin/net/ostp/client/OstpClientSdk.kt +++ b/ostp-flutter/android/app/src/main/kotlin/net/ostp/client/OstpClientSdk.kt @@ -50,4 +50,8 @@ object OstpClientSdk { @Keep @JvmStatic external fun notifyNetworkChanged() + + @Keep + @JvmStatic + external fun nativeRunDnsProber(domain: String): String } diff --git a/ostp-flutter/assets/logo.svg b/ostp-flutter/assets/logo.svg new file mode 100644 index 0000000..237b566 --- /dev/null +++ b/ostp-flutter/assets/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/ostp-flutter/lib/main.dart b/ostp-flutter/lib/main.dart index 81def1a..410bd0d 100644 --- a/ostp-flutter/lib/main.dart +++ b/ostp-flutter/lib/main.dart @@ -26,11 +26,11 @@ class OstpApp extends StatelessWidget { debugShowCheckedModeBanner: false, theme: ThemeData( brightness: Brightness.dark, - scaffoldBackgroundColor: const Color(0xFF08080F), + scaffoldBackgroundColor: const Color(0xFF030303), colorScheme: const ColorScheme.dark( - primary: Color(0xFF6C72FF), - secondary: Color(0xFF22D3A5), - surface: Color(0xFF151522), + primary: Color(0xFFF9FAFB), + secondary: Color(0xFF10B981), + surface: Color(0xFF09090B), ), fontFamily: 'Inter', useMaterial3: true, diff --git a/ostp-flutter/lib/ui/home_screen.dart b/ostp-flutter/lib/ui/home_screen.dart index 3d880bc..02bd3c3 100644 --- a/ostp-flutter/lib/ui/home_screen.dart +++ b/ostp-flutter/lib/ui/home_screen.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import '../models/connection_state_enum.dart'; import 'settings_screen.dart'; import 'logs_screen.dart'; @@ -517,31 +518,16 @@ class _HomeScreenState extends State with TickerProviderStateMixin { return Scaffold( body: Stack( children: [ - Positioned( - top: -150, right: -100, - child: Container( - width: 400, height: 400, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: theme.colorScheme.primary.withOpacity(0.15), - ), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100), - child: Container(), - ), - ), - ), - Positioned( - bottom: -100, left: -100, - child: Container( - width: 350, height: 350, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: theme.colorScheme.secondary.withOpacity(0.1), - ), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100), - child: Container(), + Center( + child: Opacity( + opacity: theme.brightness == Brightness.dark ? 0.05 : 0.06, + child: SvgPicture.asset( + 'assets/logo.svg', + width: MediaQuery.of(context).size.width * 0.8, + fit: BoxFit.contain, + colorFilter: theme.brightness == Brightness.light + ? const ColorFilter.mode(Colors.black, BlendMode.srcIn) + : null, ), ), ), diff --git a/ostp-flutter/lib/ui/settings_screen.dart b/ostp-flutter/lib/ui/settings_screen.dart index 06281f0..8e9732c 100644 --- a/ostp-flutter/lib/ui/settings_screen.dart +++ b/ostp-flutter/lib/ui/settings_screen.dart @@ -38,7 +38,7 @@ class _SettingsScreenState extends State { bool _obscureKey = true; bool _debugMode = false; - String _dnsRegion = 'Global'; + late TextEditingController _dnsRegionCtrl; String _transportMode = 'udp'; // 'udp' | 'uot' String _tunStack = 'ostp'; // 'system' | 'ostp' bool _muxEnabled = false; @@ -58,9 +58,9 @@ class _SettingsScreenState extends State { _ipsCtrl = TextEditingController(text: widget.prefs.getString('ex_ips') ?? ''); _processesCtrl = TextEditingController(text: widget.prefs.getString('ex_processes') ?? ''); _dnsDomainCtrl = TextEditingController(text: widget.prefs.getString('dns_domain') ?? ''); - _pbkCtrl = TextEditingController(text: widget.prefs.getString('pbk') ?? ''); + _dnsRegionCtrl = TextEditingController(text: widget.prefs.getString('dns_region') ?? '1.1.1.1'); + _pbkCtrl = TextEditingController(text: widget.prefs.getString('tun_pbk') ?? ''); _sidCtrl = TextEditingController(text: widget.prefs.getString('sid') ?? ''); - _dnsRegion = widget.prefs.getString('dns_region') ?? 'Global'; _transportMode = widget.prefs.getString('transport_mode') ?? 'udp'; _tunStack = widget.prefs.getString('tun_stack') ?? 'ostp'; _debugMode = widget.prefs.getBool('debug_mode') ?? false; @@ -81,6 +81,7 @@ class _SettingsScreenState extends State { _ipsCtrl.dispose(); _processesCtrl.dispose(); _dnsDomainCtrl.dispose(); + _dnsRegionCtrl.dispose(); _pbkCtrl.dispose(); _sidCtrl.dispose(); _muxSessionsCtrl.dispose(); @@ -97,11 +98,11 @@ class _SettingsScreenState extends State { widget.prefs.setString('ex_ips', _ipsCtrl.text.trim()); widget.prefs.setString('ex_processes', _processesCtrl.text.trim()); widget.prefs.setBool('debug_mode', _debugMode); - widget.prefs.setString('dns_region', _dnsRegion); widget.prefs.setString('transport_mode', _transportMode); widget.prefs.setString('tun_stack', _tunStack); widget.prefs.setString('dns_domain', _dnsDomainCtrl.text.trim()); - widget.prefs.setString('pbk', _pbkCtrl.text.trim()); + widget.prefs.setString('dns_region', _dnsRegionCtrl.text.trim()); + widget.prefs.setString('tun_pbk', _pbkCtrl.text.trim()); widget.prefs.setString('sid', _sidCtrl.text.trim()); widget.prefs.setBool('mux_enabled', _muxEnabled); widget.prefs.setString('mux_sessions', _muxSessionsCtrl.text.trim()); @@ -237,7 +238,7 @@ class _SettingsScreenState extends State { _serverCtrl.text = host; _keyCtrl.text = key; _dnsDomainCtrl.text = uri.queryParameters['domain'] ?? ''; - _dnsRegion = uri.queryParameters['region'] ?? 'Global'; + _dnsRegionCtrl.text = uri.queryParameters['resolver'] ?? '1.1.1.1'; final type = uri.queryParameters['type']; _transportMode = type == 'tcp' || type == 'http' ? 'uot' : (type == 'dns' ? 'dns' : 'udp'); @@ -349,30 +350,27 @@ class _SettingsScreenState extends State { const SizedBox(height: 16), _buildTextField('Domain (Points to Server)', _dnsDomainCtrl, hint: 'tunnel.myvpn.com'), const SizedBox(height: 16), - DropdownButtonFormField( - value: _dnsRegion, - dropdownColor: const Color(0xFF1E1E2C), - style: const TextStyle(color: Colors.white, fontSize: 14), - decoration: InputDecoration( - labelText: 'DNS Resolver Region', - labelStyle: const TextStyle(color: Colors.white54, fontSize: 13), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - ), - items: ['Global', 'Russia', 'China', 'Iran'].map((String region) { - return DropdownMenuItem( - value: region, - child: Text(region), - ); - }).toList(), - onChanged: (String? newValue) { - if (newValue != null) { - setState(() { - _dnsRegion = newValue; - _saveSettings(); - }); - } - }, + Row( + children: [ + Expanded( + child: _buildTextField('DNS Resolver Server', _dnsRegionCtrl, hint: '1.1.1.1'), + ), + const SizedBox(width: 8), + Padding( + padding: const EdgeInsets.only(top: 24.0), + child: ElevatedButton( + onPressed: _showDnsProberDialog, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orangeAccent.withOpacity(0.2), + foregroundColor: Colors.orangeAccent, + elevation: 0, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + child: const Text('PROBER', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12)), + ), + ) + ], ), ], ), @@ -603,6 +601,97 @@ class _SettingsScreenState extends State { ); } + Future _showDnsProberDialog() async { + const channel = MethodChannel('com.ospab.ostp/vpn'); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return StatefulBuilder( + builder: (context, setModalState) { + return AlertDialog( + backgroundColor: Theme.of(context).colorScheme.surface, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + title: const Text('DNS Prober', textAlign: TextAlign.center), + content: FutureBuilder( + future: channel.invokeMethod('runDnsProber', {'domain': _dnsDomainCtrl.text.trim()}), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('Sending real tunnel probes...', style: TextStyle(color: Colors.white54, fontSize: 13), textAlign: TextAlign.center), + ], + ); + } + + if (snapshot.hasError || !snapshot.hasData) { + return Text('Error: ${snapshot.error}', style: const TextStyle(color: Colors.redAccent)); + } + + List results = []; + try { + results = jsonDecode(snapshot.data!); + } catch (_) {} + + if (results.isEmpty) { + return const Text('No results or all timed out.', style: TextStyle(color: Colors.redAccent)); + } + + return SizedBox( + width: double.maxFinite, + child: ListView.builder( + shrinkWrap: true, + itemCount: results.length, + itemBuilder: (context, index) { + final res = results[index]; + final name = res['name'] ?? ''; + final ip = res['ip'] ?? ''; + final latency = res['latency_ms']; + + final isBest = index == 0 && latency != null; + + return ListTile( + onTap: latency != null ? () { + setState(() { + _dnsRegionCtrl.text = ip; + _saveSettings(); + }); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('DNS set to $ip'))); + } : null, + title: Text('${isBest ? '⭐ ' : ''}$name', style: const TextStyle(fontSize: 14)), + subtitle: Text(ip, style: const TextStyle(fontSize: 12, color: Colors.white54)), + trailing: Text( + latency != null ? '$latency ms' : 'TIMEOUT', + style: TextStyle( + color: latency == null ? Colors.redAccent : (latency < 100 ? Colors.greenAccent : Colors.orangeAccent), + fontWeight: FontWeight.bold, + ), + ), + tileColor: isBest ? Colors.blueAccent.withOpacity(0.1) : null, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ); + }, + ), + ); + }, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ) + ], + ); + } + ); + } + ); + } + Future _checkForUpdates() async { if (_isCheckingUpdates) return; setState(() { _isCheckingUpdates = true; }); diff --git a/ostp-flutter/pubspec.lock b/ostp-flutter/pubspec.lock index aee1865..0d2d39d 100644 --- a/ostp-flutter/pubspec.lock +++ b/ostp-flutter/pubspec.lock @@ -134,6 +134,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" + url: "https://pub.dev" + source: hosted + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -272,6 +280,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" path_provider_linux: dependency: transitive description: @@ -581,6 +597,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.5" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "142a9146f447d15b10bdc00e21d5f4d83e5b32bb5f8f8f5a04c75311344923a3" + url: "https://pub.dev" + source: hosted + version: "1.2.6" vector_math: dependency: transitive description: diff --git a/ostp-flutter/pubspec.yaml b/ostp-flutter/pubspec.yaml index 745ebc0..35b8856 100644 --- a/ostp-flutter/pubspec.yaml +++ b/ostp-flutter/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.3.7+20 +version: 0.3.8+21 environment: sdk: ^3.11.4 @@ -34,6 +34,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + flutter_svg: ^2.0.10 shared_preferences: ^2.5.5 mobile_scanner: ^5.0.0 window_manager: ^0.5.1 @@ -72,9 +73,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/logo.svg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images diff --git a/ostp-gui/src-tauri/Cargo.lock b/ostp-gui/src-tauri/Cargo.lock index 8fad9f8..4b140bd 100644 --- a/ostp-gui/src-tauri/Cargo.lock +++ b/ostp-gui/src-tauri/Cargo.lock @@ -2665,7 +2665,7 @@ dependencies = [ [[package]] name = "ostp-client" -version = "0.2.98" +version = "0.3.8" dependencies = [ "anyhow", "base64 0.22.1", @@ -2700,17 +2700,20 @@ dependencies = [ [[package]] name = "ostp-core" -version = "0.2.98" +version = "0.3.8" dependencies = [ "anyhow", + "byteorder", "bytes", "chacha20poly1305", "hkdf", "hmac", "rand", + "serde", "sha2", "snow", "thiserror 1.0.69", + "tokio", "tracing", "x25519-dalek", ] @@ -2720,12 +2723,16 @@ name = "ostp-gui" version = "0.1.0" dependencies = [ "anyhow", + "chacha20poly1305", + "hex", "json_comments", "ostp-client", + "ostp-core", "portable-atomic", "rand", "serde", "serde_json", + "sha2", "tauri", "tauri-build", "tauri-plugin-opener", @@ -2735,7 +2742,7 @@ dependencies = [ [[package]] name = "ostp-tun" -version = "0.2.98" +version = "0.3.8" dependencies = [ "anyhow", "libc", diff --git a/ostp-gui/src-tauri/Cargo.toml b/ostp-gui/src-tauri/Cargo.toml index b4a59c0..09201f3 100644 --- a/ostp-gui/src-tauri/Cargo.toml +++ b/ostp-gui/src-tauri/Cargo.toml @@ -26,6 +26,7 @@ tokio = { version = "1", features = ["full"] } anyhow = "1" tracing = "0.1" ostp-client = { path = "../../ostp-client" } +ostp-core = { path = "../../ostp-core" } portable-atomic = "1" json_comments = "0.2" rand = "0.8" diff --git a/ostp-gui/src-tauri/icons/128x128.png b/ostp-gui/src-tauri/icons/128x128.png index 288a745..c13ab18 100644 Binary files a/ostp-gui/src-tauri/icons/128x128.png and b/ostp-gui/src-tauri/icons/128x128.png differ diff --git a/ostp-gui/src-tauri/icons/128x128@2x.png b/ostp-gui/src-tauri/icons/128x128@2x.png index 587aeaf..41ee307 100644 Binary files a/ostp-gui/src-tauri/icons/128x128@2x.png and b/ostp-gui/src-tauri/icons/128x128@2x.png differ diff --git a/ostp-gui/src-tauri/icons/32x32.png b/ostp-gui/src-tauri/icons/32x32.png index 661088c..fa80533 100644 Binary files a/ostp-gui/src-tauri/icons/32x32.png and b/ostp-gui/src-tauri/icons/32x32.png differ diff --git a/ostp-gui/src-tauri/icons/64x64.png b/ostp-gui/src-tauri/icons/64x64.png index 4a473cc..22284c3 100644 Binary files a/ostp-gui/src-tauri/icons/64x64.png and b/ostp-gui/src-tauri/icons/64x64.png differ diff --git a/ostp-gui/src-tauri/icons/Square107x107Logo.png b/ostp-gui/src-tauri/icons/Square107x107Logo.png index a2c841f..1aa516a 100644 Binary files a/ostp-gui/src-tauri/icons/Square107x107Logo.png and b/ostp-gui/src-tauri/icons/Square107x107Logo.png differ diff --git a/ostp-gui/src-tauri/icons/Square142x142Logo.png b/ostp-gui/src-tauri/icons/Square142x142Logo.png index 38d8fbb..bf4d7c5 100644 Binary files a/ostp-gui/src-tauri/icons/Square142x142Logo.png and b/ostp-gui/src-tauri/icons/Square142x142Logo.png differ diff --git a/ostp-gui/src-tauri/icons/Square150x150Logo.png b/ostp-gui/src-tauri/icons/Square150x150Logo.png index f8ad116..ec5b9f7 100644 Binary files a/ostp-gui/src-tauri/icons/Square150x150Logo.png and b/ostp-gui/src-tauri/icons/Square150x150Logo.png differ diff --git a/ostp-gui/src-tauri/icons/Square284x284Logo.png b/ostp-gui/src-tauri/icons/Square284x284Logo.png index 6c9890c..50b3f8e 100644 Binary files a/ostp-gui/src-tauri/icons/Square284x284Logo.png and b/ostp-gui/src-tauri/icons/Square284x284Logo.png differ diff --git a/ostp-gui/src-tauri/icons/Square30x30Logo.png b/ostp-gui/src-tauri/icons/Square30x30Logo.png index ff3b36c..f36c81a 100644 Binary files a/ostp-gui/src-tauri/icons/Square30x30Logo.png and b/ostp-gui/src-tauri/icons/Square30x30Logo.png differ diff --git a/ostp-gui/src-tauri/icons/Square310x310Logo.png b/ostp-gui/src-tauri/icons/Square310x310Logo.png index c001513..f1e95d2 100644 Binary files a/ostp-gui/src-tauri/icons/Square310x310Logo.png and b/ostp-gui/src-tauri/icons/Square310x310Logo.png differ diff --git a/ostp-gui/src-tauri/icons/Square44x44Logo.png b/ostp-gui/src-tauri/icons/Square44x44Logo.png index e19b86d..70c32fb 100644 Binary files a/ostp-gui/src-tauri/icons/Square44x44Logo.png and b/ostp-gui/src-tauri/icons/Square44x44Logo.png differ diff --git a/ostp-gui/src-tauri/icons/Square71x71Logo.png b/ostp-gui/src-tauri/icons/Square71x71Logo.png index ea6e694..ed62559 100644 Binary files a/ostp-gui/src-tauri/icons/Square71x71Logo.png and b/ostp-gui/src-tauri/icons/Square71x71Logo.png differ diff --git a/ostp-gui/src-tauri/icons/Square89x89Logo.png b/ostp-gui/src-tauri/icons/Square89x89Logo.png index 13a9768..5d3c1a7 100644 Binary files a/ostp-gui/src-tauri/icons/Square89x89Logo.png and b/ostp-gui/src-tauri/icons/Square89x89Logo.png differ diff --git a/ostp-gui/src-tauri/icons/StoreLogo.png b/ostp-gui/src-tauri/icons/StoreLogo.png index 43e969c..6303f1f 100644 Binary files a/ostp-gui/src-tauri/icons/StoreLogo.png and b/ostp-gui/src-tauri/icons/StoreLogo.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png index 8a80a4b..3c8ca97 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png and b/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png index 57f064a..c0cd7e2 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png and b/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png index e6dc569..72a1467 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png and b/ostp-gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png index 7e1574b..b214536 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png and b/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png index 6378ca3..93cec82 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png and b/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png index 8d0a6a9..ec6c5c3 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png and b/ostp-gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png index e6dd7d2..a09ae41 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png and b/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png index f0981ee..8cbe008 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png and b/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png index f90e96a..adde42b 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png and b/ostp-gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png index ab7431c..12d63df 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png and b/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png index fe9b6ff..403c392 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png and b/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png index fbcab6e..415d83a 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png and b/ostp-gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png index a63ebfc..8ce78b3 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png and b/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png index 40c4f41..03d0e24 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png and b/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png index 7e22e75..2694407 100644 Binary files a/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png and b/ostp-gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/ostp-gui/src-tauri/icons/icon.icns b/ostp-gui/src-tauri/icons/icon.icns index b8c2431..2ac04b6 100644 Binary files a/ostp-gui/src-tauri/icons/icon.icns and b/ostp-gui/src-tauri/icons/icon.icns differ diff --git a/ostp-gui/src-tauri/icons/icon.ico b/ostp-gui/src-tauri/icons/icon.ico index 7f0dffa..9161f2f 100644 Binary files a/ostp-gui/src-tauri/icons/icon.ico and b/ostp-gui/src-tauri/icons/icon.ico differ diff --git a/ostp-gui/src-tauri/icons/icon.png b/ostp-gui/src-tauri/icons/icon.png index 5060731..fe1265b 100644 Binary files a/ostp-gui/src-tauri/icons/icon.png and b/ostp-gui/src-tauri/icons/icon.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@1x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@1x.png index 6f3bacb..ef8161e 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@1x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png index ae97708..2953fa8 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@2x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@2x.png index ae97708..2953fa8 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@2x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@3x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@3x.png index 61e6f4f..2e243e7 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@3x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@1x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@1x.png index d1c13bd..b4b2898 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@1x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png index d110d78..1cf8033 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@2x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@2x.png index d110d78..1cf8033 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@2x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@3x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@3x.png index e368061..0738092 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@3x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@1x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@1x.png index ae97708..2953fa8 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@1x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png index f3af132..4e2ea27 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@2x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@2x.png index f3af132..4e2ea27 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@2x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@3x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@3x.png index d64dec8..826fe09 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@3x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-512@2x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-512@2x.png index a456806..7a5f0f9 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-512@2x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-60x60@2x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-60x60@2x.png index d64dec8..826fe09 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-60x60@2x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-60x60@3x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-60x60@3x.png index 1149dfb..c90773a 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-60x60@3x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-76x76@1x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-76x76@1x.png index e158039..2500ca8 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-76x76@1x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-76x76@2x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-76x76@2x.png index d418f59..ab4256b 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-76x76@2x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/ostp-gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/ostp-gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png index 5b10e7d..1277ed9 100644 Binary files a/ostp-gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and b/ostp-gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/ostp-gui/src-tauri/permissions/app-commands.toml b/ostp-gui/src-tauri/permissions/app-commands.toml index aae11a1..30cb2fc 100644 --- a/ostp-gui/src-tauri/permissions/app-commands.toml +++ b/ostp-gui/src-tauri/permissions/app-commands.toml @@ -6,11 +6,15 @@ description = "Enables access to core OSTP commands" allow = [ "start_tunnel", "stop_tunnel", + "reload_tunnel", "get_tunnel_status", "get_metrics", "get_config", "save_config", "get_wintun_install_path", "set_autostart", - "get_autostart" + "get_autostart", + "list_running_processes", + "kill_auto_search", + "run_dns_prober" ] diff --git a/ostp-gui/src-tauri/src/dns_prober.rs b/ostp-gui/src-tauri/src/dns_prober.rs new file mode 100644 index 0000000..f2043b4 --- /dev/null +++ b/ostp-gui/src-tauri/src/dns_prober.rs @@ -0,0 +1,6 @@ +use ostp_core::dns_prober::{run_dns_prober as core_run_dns_prober, DnsProbeResult}; + +#[tauri::command] +pub async fn run_dns_prober(domain: String) -> Result, String> { + core_run_dns_prober(&domain).await +} diff --git a/ostp-gui/src-tauri/src/lib.rs b/ostp-gui/src-tauri/src/lib.rs index 91afa05..646eeb8 100644 --- a/ostp-gui/src-tauri/src/lib.rs +++ b/ostp-gui/src-tauri/src/lib.rs @@ -8,6 +8,7 @@ use portable_atomic::Ordering; use tauri::Emitter; mod ipc_crypto; +mod dns_prober; // ── Config types ───────────────────────────────────────────────────────────── @@ -778,7 +779,7 @@ static SINGLE_INSTANCE_LOCK: std::sync::OnceLock = std::s pub fn run() { if let Ok(listener) = std::net::TcpListener::bind("127.0.0.1:49153") { let _ = SINGLE_INSTANCE_LOCK.set(listener); - } else { + } else if !cfg!(debug_assertions) { show_error_dialog("ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ OSTP GUI ΡƒΠΆΠ΅ Π·Π°ΠΏΡƒΡ‰Π΅Π½ΠΎ!"); return; } @@ -874,7 +875,7 @@ pub fn run() { } _ => {} }) - .invoke_handler(tauri::generate_handler![start_tunnel, stop_tunnel, reload_tunnel, get_tunnel_status, get_metrics, get_config, save_config, get_wintun_install_path, set_autostart, get_autostart, list_running_processes]) + .invoke_handler(tauri::generate_handler![start_tunnel, stop_tunnel, reload_tunnel, get_tunnel_status, get_metrics, get_config, save_config, get_wintun_install_path, set_autostart, get_autostart, list_running_processes, dns_prober::run_dns_prober]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/ostp-gui/src-tauri/tauri.conf.json b/ostp-gui/src-tauri/tauri.conf.json index 5b1f214..a65c1e4 100644 --- a/ostp-gui/src-tauri/tauri.conf.json +++ b/ostp-gui/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ostp-gui", - "version": "0.3.7", + "version": "0.3.8", "identifier": "com.ospab.ostp", "build": { "frontendDist": "../src" diff --git a/ostp-gui/src/assets/logo.svg b/ostp-gui/src/assets/logo.svg new file mode 100644 index 0000000..237b566 --- /dev/null +++ b/ostp-gui/src/assets/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/ostp-gui/src/index.html b/ostp-gui/src/index.html index 88cbf21..6263cf8 100644 --- a/ostp-gui/src/index.html +++ b/ostp-gui/src/index.html @@ -12,10 +12,9 @@
- - -
+ + + + + diff --git a/ostp-gui/src/main.js b/ostp-gui/src/main.js index aeaa0c4..40f8696 100644 --- a/ostp-gui/src/main.js +++ b/ostp-gui/src/main.js @@ -75,7 +75,7 @@ function bindSettingsInputs() { if (inTransport) { inTransport.addEventListener('change', () => { if (inTransport.value === 'dns') { - groupDnsProxy.style.display = 'block'; + groupDnsProxy.style.display = 'flex'; } else { groupDnsProxy.style.display = 'none'; } @@ -88,6 +88,86 @@ const btnWintunCancel = $('btn-wintun-cancel'); const btnWintunOpen = $('btn-wintun-open'); const wintunInstallPath = $('wintun-install-path'); +const dnsProberModal = $('dns-prober-modal'); +const proberStatus = $('prober-status'); +const proberList = $('prober-list'); +const btnProberClose = $('btn-prober-close'); +const btnDnsProber = $('btn-dns-prober'); + +// ── DNS Prober ─────────────────────────────────────────────────────────────── +async function openDnsProber() { + dnsProberModal.classList.remove('hidden'); + proberList.innerHTML = ''; + proberStatus.textContent = 'Running probes...'; + + const domain = inDnsDomain?.value?.trim() || 'example.com'; + + let results; + try { + results = await invoke('run_dns_prober', { domain }); + } catch (err) { + proberStatus.textContent = 'Error: ' + err; + return; + } + + proberList.innerHTML = ''; + + if (!results || results.length === 0) { + proberStatus.textContent = 'No results.'; + return; + } + + let bestIp = null; + + results.forEach((r, i) => { + const isBest = i === 0 && r.latency_ms != null; + if (isBest && !bestIp) bestIp = r.ip; + + const row = document.createElement('div'); + row.style.cssText = ` + display: flex; align-items: center; justify-content: space-between; + padding: 6px 10px; border-radius: 6px; cursor: pointer; + background: ${isBest ? 'rgba(99,179,237,0.12)' : 'rgba(255,255,255,0.04)'}; + border: 1px solid ${isBest ? 'rgba(99,179,237,0.35)' : 'transparent'}; + transition: background 0.15s; + `; + + const latText = r.latency_ms != null ? `${r.latency_ms} ms` : 'TIMEOUT'; + const latColor = r.latency_ms == null ? '#f56565' + : r.latency_ms < 50 ? '#68d391' + : r.latency_ms < 150 ? '#f6e05e' + : '#fc8181'; + + row.innerHTML = ` + ${isBest ? '⭐ ' : ''}${r.name} + ${r.ip} + ${latText} + `; + + if (r.latency_ms != null) { + row.addEventListener('click', () => { + inDnsRegion.value = r.ip; + scheduleAutoSave(); + dnsProberModal.classList.add('hidden'); + showToast('DNS server set to ' + r.ip, 'ok'); + }); + row.addEventListener('mouseenter', () => { row.style.background = 'rgba(99,179,237,0.18)'; }); + row.addEventListener('mouseleave', () => { row.style.background = isBest ? 'rgba(99,179,237,0.12)' : 'rgba(255,255,255,0.04)'; }); + } + + proberList.appendChild(row); + }); + + if (bestIp) { + proberStatus.textContent = `βœ“ Best: ${bestIp} β€” click any row to select`; + // Auto-fill best + inDnsRegion.value = bestIp; + scheduleAutoSave(); + } else { + proberStatus.textContent = 'All servers timed out.'; + } +} + // ── Tag-input state ─────────────────────────────────────────────────────────── // Map of tagId -> Set const tagState = { @@ -366,7 +446,7 @@ async function loadConfigIntoForm() { inKey.value = ostpOut.access_key || ''; inTransport.value = ostpOut.transport?.type || 'udp'; if (inTransport.value === 'dns') { - groupDnsProxy.style.display = 'block'; + groupDnsProxy.style.display = 'flex'; inDnsDomain.value = ostpOut.transport?.domain || ''; inDnsRegion.value = ostpOut.transport?.resolver || 'Global'; } else { @@ -592,6 +672,7 @@ window.addEventListener('DOMContentLoaded', async () => { applyTranslations(); setState('disconnected'); updateKillSwitchVisibility(); + bindSettingsInputs(); // Event wiring if (window.__TAURI__ && window.__TAURI__.event) { @@ -737,6 +818,22 @@ window.addEventListener('DOMContentLoaded', async () => { wintunModal.classList.add('hidden'); }); + // DNS Prober modal + if (btnDnsProber) { + btnDnsProber.addEventListener('click', openDnsProber); + } + if (btnProberClose) { + btnProberClose.addEventListener('click', () => { + dnsProberModal.classList.add('hidden'); + }); + } + // Close prober on backdrop click + if (dnsProberModal) { + dnsProberModal.addEventListener('click', (e) => { + if (e.target === dnsProberModal) dnsProberModal.classList.add('hidden'); + }); + } + // Open wintun.net link β€” handled natively by , but also wire as fallback if (btnWintunOpen && window.__TAURI__) { btnWintunOpen.addEventListener('click', (e) => { diff --git a/ostp-gui/src/styles.css b/ostp-gui/src/styles.css index 541b0e2..efa4ad6 100644 --- a/ostp-gui/src/styles.css +++ b/ostp-gui/src/styles.css @@ -5,27 +5,27 @@ /* ── Tokens β€” Dark (default) ─────────────────────────────────────────────── */ :root { - /* Colors */ - --c-bg: #08080f; - --c-surface: #0f0f1a; - --c-card: rgba(255,255,255,0.04); - --c-card-border: rgba(255,255,255,0.07); + /* Colors - Dark (default) */ + --c-bg: #030303; + --c-surface: #09090b; + --c-card: rgba(255,255,255,0.02); + --c-card-border: rgba(255,255,255,0.05); - --c-accent: #6c72ff; - --c-accent-2: #a78bfa; - --c-accent-glow: rgba(108,114,255,0.35); - --c-accent-dim: rgba(108,114,255,0.12); + --c-accent: #52525b; + --c-accent-2: #71717a; + --c-accent-glow: rgba(255,255,255,0.05); + --c-accent-dim: rgba(255,255,255,0.03); - --c-green: #22d3a5; - --c-green-glow: rgba(34,211,165,0.35); - --c-green-dim: rgba(34,211,165,0.10); + --c-green: #10b981; + --c-green-glow: rgba(16,185,129,0.25); + --c-green-dim: rgba(16,185,129,0.10); --c-amber: #f59e0b; - --c-red: #f87171; + --c-red: #ef4444; - --c-txt-1: #e2e4f0; - --c-txt-2: #636882; - --c-txt-3: #343647; + --c-txt-1: #fdfdfd; + --c-txt-2: #a1a1aa; + --c-txt-3: #52525b; /* Radii */ --r-xs: 6px; @@ -46,26 +46,26 @@ /* ── Tokens β€” Light theme override ────────────────────────────────────────── */ [data-theme="light"] { - --c-bg: #f0f2fa; + --c-bg: #f4f4f5; --c-surface: #ffffff; - --c-card: rgba(255,255,255,0.75); - --c-card-border: rgba(0,0,0,0.08); + --c-card: rgba(0,0,0,0.02); + --c-card-border: rgba(0,0,0,0.06); - --c-accent: #5b61f0; - --c-accent-2: #8b5cf6; - --c-accent-glow: rgba(91,97,240,0.25); - --c-accent-dim: rgba(91,97,240,0.10); + --c-accent: #e4e4e7; + --c-accent-2: #d4d4d8; + --c-accent-glow: rgba(0,0,0,0.05); + --c-accent-dim: rgba(0,0,0,0.03); - --c-green: #10b981; - --c-green-glow: rgba(16,185,129,0.25); - --c-green-dim: rgba(16,185,129,0.10); + --c-green: #059669; + --c-green-glow: rgba(5,150,105,0.2); + --c-green-dim: rgba(5,150,105,0.1); --c-amber: #d97706; - --c-red: #ef4444; + --c-red: #dc2626; - --c-txt-1: #0f1020; - --c-txt-2: #5a5f7a; - --c-txt-3: #b0b4cc; + --c-txt-1: #09090b; + --c-txt-2: #52525b; + --c-txt-3: #a1a1aa; } /* ── Light theme element overrides ───────────────────────────────────────── */ @@ -74,9 +74,6 @@ html[data-theme="light"] body { background: var(--c-bg); } -html[data-theme="light"] .blob-1 { opacity: 0.08; } -html[data-theme="light"] .blob-2 { opacity: 0.06; } - html[data-theme="light"] .power-btn { background: var(--c-surface); box-shadow: @@ -198,41 +195,32 @@ input, textarea { font-family: inherit; } overflow: hidden; } -/* ── Ambient blobs ────────────────────────────────────────────────────────── */ -.ambient { +/* ── Watermark ────────────────────────────────────────────────────────────── */ +.watermark { position: absolute; - inset: 0; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 80%; + max-width: 600px; pointer-events: none; z-index: 0; - overflow: hidden; + opacity: 0.05; + user-select: none; + display: flex; + align-items: center; + justify-content: center; } -.blob { - position: absolute; - border-radius: 50%; - filter: blur(100px); - will-change: transform; +.watermark img { + width: 100%; + height: auto; + object-fit: contain; } -.blob-1 { - width: 400px; height: 400px; - background: var(--c-accent); - opacity: 0.15; - top: -150px; right: -100px; - animation: blob-drift 28s infinite alternate ease-in-out; -} - -.blob-2 { - width: 350px; height: 350px; - background: var(--c-green); - opacity: 0.10; - bottom: -100px; left: -100px; - animation: blob-drift 22s infinite alternate-reverse ease-in-out; -} - -@keyframes blob-drift { - from { transform: translate(0, 0); } - to { transform: translate(30px, 20px); } +html[data-theme="light"] .watermark { + opacity: 0.06; + filter: invert(1); } /* ── Screen system ────────────────────────────────────────────────────────── */ diff --git a/ostp-jni/src/lib.rs b/ostp-jni/src/lib.rs index 9bada78..34af172 100644 --- a/ostp-jni/src/lib.rs +++ b/ostp-jni/src/lib.rs @@ -368,3 +368,31 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_notifyNetworkChanged( // No-op for now; multi-server handles network drops via keep-alives and reconnection } + +#[no_mangle] +pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeRunDnsProber( + mut env: JNIEnv, + _class: JClass, + domain: JString, +) -> jstring { + let domain_str: String = match env.get_string(&domain) { + Ok(s) => s.into(), + Err(_) => return env.new_string("[]").unwrap().into_raw(), + }; + + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + let result = rt.block_on(async { + ostp_core::dns_prober::run_dns_prober(&domain_str).await + }); + + let json = match result { + Ok(res) => serde_json::to_string(&res).unwrap_or_else(|_| "[]".to_string()), + Err(_) => "[]".to_string(), + }; + + env.new_string(json).unwrap().into_raw() +} diff --git a/ostp-server/src/config.rs b/ostp-server/src/config.rs index ee12f69..2d5514a 100644 --- a/ostp-server/src/config.rs +++ b/ostp-server/src/config.rs @@ -27,6 +27,15 @@ pub enum ServerInbound { username: Option, #[serde(default, skip_serializing_if = "Option::is_none")] password_hash: Option, + }, + Dns { + tag: String, + listen: String, + domain: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pubkey: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + privkey: Option, } } @@ -121,9 +130,6 @@ pub struct ModularServerConfig { pub dns: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub license_key: Option, - - #[serde(default, skip_serializing_if = "Option::is_none")] - pub dns_transport: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] diff --git a/ostp-wiki/DNS-Transport.md b/ostp-wiki/DNS-Transport.md deleted file mode 100644 index 0bcb9c3..0000000 --- a/ostp-wiki/DNS-Transport.md +++ /dev/null @@ -1,73 +0,0 @@ -# OSTP DNS Transport (ПослСдний Π ΡƒΠ±Π΅ΠΆ) - -DNS Transport (DNS ΠŸΡ€ΠΎΠΊΡΠΈ) β€” это ΡΠΊΡΠΏΠ΅Ρ€ΠΈΠΌΠ΅Π½Ρ‚Π°Π»ΡŒΠ½Ρ‹ΠΉ транспорт, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ являСтся "послСдним Ρ€ΡƒΠ±Π΅ΠΆΠΎΠΌ" для ΠΎΠ±Ρ…ΠΎΠ΄Π° Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ. Он ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ, ΠΊΠΎΠ³Π΄Π° всС ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»Ρ‹ (UDP, UoT/TCP) ΠΏΠΎΠ»Π½ΠΎΡΡ‚ΡŒΡŽ Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½Ρ‹ ΠΏΡ€ΠΎΠ²Π°ΠΉΠ΄Π΅Ρ€ΠΎΠΌ ΠΈΠ»ΠΈ DPI. - -Он маскируСт вСсь VPN-Ρ‚Ρ€Π°Ρ„ΠΈΠΊ ΠΏΠΎΠ΄ ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹Π΅ запросы ΠΊ DNS-сСрвСрам (Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ ΠΈΠΌΠ΅Π½), Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚ Π΅Π³ΠΎ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΡƒ практичСски Π½Π΅Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΠΉ Π±Π΅Π· ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π° Π² Ρ†Π΅Π»ΠΎΠΌ. OSTP ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ запросы Ρ‚ΠΈΠΏΠ° `TXT` ΠΈ `NULL` для ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π΄Π°Π½Π½Ρ‹Ρ…, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ ΠΊΠΎΠ΄ΠΈΡ€ΠΎΠ²ΠΊΡƒ Base32. - -> **Π’Π½ΠΈΠΌΠ°Π½ΠΈΠ΅:** DNS-Ρ‚ΡƒΠ½Π½Π΅Π»ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π·Π½Π°Ρ‡ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΌΠ΅Π΄Π»Π΅Π½Π½Π΅Π΅ ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹Ρ… ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»ΠΎΠ² ΠΈΠ·-Π·Π° Π½Π°ΠΊΠ»Π°Π΄Π½Ρ‹Ρ… расходов ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»Π° DNS. РСкомСндуСтся ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ этот Ρ€Π΅ΠΆΠΈΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΊΠΎΠ³Π΄Π° Π΄Ρ€ΡƒΠ³ΠΈΠ΅ способы Π½Π΅ Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚. - -## Как это Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚? - -ВмСсто Ρ‚ΠΎΠ³ΠΎ Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡ‚ΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ Ρ‚Ρ€Π°Ρ„ΠΈΠΊ Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ Π½Π° ваш сСрвСр, ΠΊΠ»ΠΈΠ΅Π½Ρ‚ OSTP отправляСт стандартный DNS-запрос Π½Π° **ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹Π΅ DNS-сСрвСры** (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, 1.1.1.1, 8.8.8.8) ΠΈΠ»ΠΈ сСрвСры Π²Ρ‹Π±Ρ€Π°Π½Π½ΠΎΠ³ΠΎ Π²Π°ΠΌΠΈ Ρ€Π΅Π³ΠΈΠΎΠ½Π° (Prober). -ΠŸΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹ΠΉ сСрвСр-Ρ€Π΅Π·ΠΎΠ»Π²Π΅Ρ€ пСрСнаправляСт этот запрос Π½Π° **ваш сСрвСр** (ΠΊΠ°ΠΊ Π½Π° Π°Π²Ρ‚ΠΎΡ€ΠΈΡ‚Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ DNS-сСрвСр для вашСго Π΄ΠΎΠΌΠ΅Π½Π°), Π° ваш сСрвСр ΠΎΡ‚Π²Π΅Ρ‡Π°Π΅Ρ‚ ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎ Ρ‡Π΅Ρ€Π΅Π· Ρ€Π΅Π·ΠΎΠ»Π²Π΅Ρ€. - -Для настройки этого ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌΠ° **Π²Π°ΠΌ понадобится собствСнный Π΄ΠΎΠΌΠ΅Π½**. - ---- - -## Настройка Π½Π° сторонС сСрвСра (Π”ΠΎΠΌΠ΅Π½) - -Π’Π°ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π½Π°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ NS-записи вашСго Π΄ΠΎΠΌΠ΅Π½Π° Ρ‚Π°ΠΊ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΠ½ΠΈ ΡƒΠΊΠ°Π·Ρ‹Π²Π°Π»ΠΈ Π½Π° IP-адрСс вашСго OSTP сСрвСра. - -НапримСр, Π²Ρ‹ Π²Π»Π°Π΄Π΅Π΅Ρ‚Π΅ Π΄ΠΎΠΌΠ΅Π½ΠΎΠΌ `myvpn.com` ΠΈ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΠΎΠ΄Π΄ΠΎΠΌΠ΅Π½ `t.myvpn.com` для туннСля, Π° IP вашСго сСрвСра β€” `192.168.1.100`. - -Π’ ΠΏΠ°Π½Π΅Π»ΠΈ управлСния вашСго DNS-рСгистратора Π΄ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ записи: - -1. **A-запись:** - - Имя (Host): `ns.myvpn.com` - - Π’ΠΈΠΏ (Type): `A` - - Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ (Value): `192.168.1.100` (IP вашСго OSTP-сСрвСра) - -2. **NS-запись:** - - Имя (Host): `t.myvpn.com` - - Π’ΠΈΠΏ (Type): `NS` - - Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ (Value): `ns.myvpn.com` - -Π’Π΅ΠΏΠ΅Ρ€ΡŒ любой DNS-запрос ΠΊ ΠΏΠΎΠ΄Π΄ΠΎΠΌΠ΅Π½Π°ΠΌ `t.myvpn.com` Π±ΡƒΠ΄Π΅Ρ‚ Π½Π°ΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒΡΡ Π½Π° ваш сСрвСр (Π½Π° ΠΏΠΎΡ€Ρ‚ 53). - ---- - -## Настройка сСрвСра OSTP - -Π’ Ρ„Π°ΠΉΠ»Π΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ вашСго OSTP сСрвСра (`config.json` ΠΈΠ»ΠΈ `server.json`) Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π²ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ΠΏΡ€ΠΎΡΠ»ΡƒΡˆΠΈΠ²Π°Π½ΠΈΠ΅ ΠΏΠΎΡ€Ρ‚Π° 53 для DNS транспорта: - -```json -{ - "mode": "server", - "dns_transport": { - "enabled": true, - "port": 53, - "domain": "t.myvpn.com" - } -} -``` - -> **Π’Π°ΠΆΠ½ΠΎ:** Для ΠΏΡ€ΠΎΡΠ»ΡƒΡˆΠΈΠ²Π°Π½ΠΈΡ ΠΏΠΎΡ€Ρ‚Π° 53 Π½Π° Linux ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ Ρ‚Ρ€Π΅Π±ΡƒΡŽΡ‚ΡΡ root-ΠΏΡ€Π°Π²Π°. Π£Π±Π΅Π΄ΠΈΡ‚Π΅ΡΡŒ, Ρ‡Ρ‚ΠΎ сСрвСр Π·Π°ΠΏΡƒΡ‰Π΅Π½ с `sudo` ΠΈΠ»ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ возмоТности `setcap` для прСдоставлСния доступа ΠΊ ΠΏΠΎΡ€Ρ‚Ρƒ: -> `sudo setcap cap_net_bind_service=+ep /path/to/ostp` - ---- - -## Настройка Π² ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ (ΠšΠ»ΠΈΠ΅Π½Ρ‚) - -Π’ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π΅ OSTP (Desktop GUI ΠΈΠ»ΠΈ Mobile): - -1. ΠŸΠ΅Ρ€Π΅ΠΉΠ΄ΠΈΡ‚Π΅ Π² **Settings (Настройки)**. -2. Π’ ΠΏΠΎΠ»Π΅ **Transport Protocol** Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ `DNS Proxy (ПослСдний Ρ€ΡƒΠ±Π΅ΠΆ)`. -3. ΠŸΠΎΡΠ²ΠΈΡ‚ΡΡ ΠΏΠΎΠ»Π΅ **Domain (Points to Server)** β€” Π²Π²Π΅Π΄ΠΈΡ‚Π΅ сюда настроСнный ΠΏΠΎΠ΄Π΄ΠΎΠΌΠ΅Π½ (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `t.myvpn.com`). -4. ПолС **DNS Resolver Region** позволяСт Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ, Ρ‡Π΅Ρ€Π΅Π· сСрвСры ΠΊΠ°ΠΊΠΎΠΉ страны/ΠΏΡ€ΠΎΠ²Π°ΠΉΠ΄Π΅Ρ€Π° Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΡΡƒΡ‰Π΅ΡΡ‚Π²Π»ΡΡ‚ΡŒΡΡ ΠΌΠ°Ρ€ΡˆΡ€ΡƒΡ‚ΠΈΠ·Π°Ρ†ΠΈΡ ΠΏΠ°ΠΊΠ΅Ρ‚ΠΎΠ² (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Global, Russia, China, Iran). ΠšΠ»ΠΈΠ΅Π½Ρ‚ (Prober) автоматичСски Π½Π°ΠΉΠ΄Π΅Ρ‚ Π½Π°ΠΈΠ±ΠΎΠ»Π΅Π΅ быстрый ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹ΠΉ Ρ€Π΅Π·ΠΎΠ»Π²Π΅Ρ€ Π² этом Ρ€Π΅Π³ΠΈΠΎΠ½Π΅. - -## ΠžΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΡ ΠΈ особСнности - -- **Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ:** Из-Π·Π° Ρ€Π°Π·ΠΌΠ΅Ρ€Π° DNS-ΠΏΠ°ΠΊΠ΅Ρ‚ΠΎΠ² ΠΈ Π·Π°Π΄Π΅Ρ€ΠΆΠ΅ΠΊ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹Ρ… сСрвСров, максимальная ΡΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ ΠΌΠΎΠΆΠ΅Ρ‚ ΡΠΎΡΡ‚Π°Π²Π»ΡΡ‚ΡŒ 1-5 ΠœΠ±ΠΈΡ‚/с. -- **Polling:** ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ DNS Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΏΠΎ ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΡƒ "Запрос-ΠžΡ‚Π²Π΅Ρ‚", ΠΊΠ»ΠΈΠ΅Π½Ρ‚ отправляСт пустыС ΠΏΠΎΠ»Π»ΠΈΠ½Π³ΠΎΠ²Ρ‹Π΅ ΠΏΠ°ΠΊΠ΅Ρ‚Ρ‹ ΠΊΠ°ΠΆΠ΄Ρ‹Π΅ 2 сСкунды, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ΡŒ сСрвСру ΠΏΠ΅Ρ€Π΅ΡΡ‹Π»Π°Ρ‚ΡŒ входящиС Π΄Π°Π½Π½Ρ‹Π΅. -- **ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° DoH/DoT:** Π’ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ вСрсии запросы ΠΊ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹ΠΌ Ρ€Π΅Π·ΠΎΠ»Π²Π΅Ρ€Π°ΠΌ ΠΎΡ‚ΠΏΡ€Π°Π²Π»ΡΡŽΡ‚ΡΡ Π² ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΠΎΠΌ Π²ΠΈΠ΄Π΅ (UDP ΠΏΠΎΡ€Ρ‚ 53). Π’ Π±ΡƒΠ΄ΡƒΡ‰ΠΈΡ… обновлСниях Π±ΡƒΠ΄Π΅Ρ‚ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π° ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° DNS over HTTPS (DoH) для Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ слоя Π·Π°Ρ‰ΠΈΡ‚Ρ‹ ΠΎΡ‚ DPI-Ρ„ΠΈΠ»ΡŒΡ‚Ρ€ΠΎΠ². diff --git a/ostp-wiki/README.md b/ostp-wiki/README.md deleted file mode 100644 index e1ded57..0000000 --- a/ostp-wiki/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# OSTP Wiki - -This repository contains the documentation and wiki pages for the Ospab Stealth Transport Protocol (OSTP). - -- [Configuration Guide](configuration_guide.md) -- [API Endpoints](api_endpoints.md) -- [DNS Transport (ПослСдний Π ΡƒΠ±Π΅ΠΆ)](DNS-Transport.md) -- [v0.3.1 Configuration Migration Guide](../docs/migration_v0_3_1.md) diff --git a/ostp-wiki/api_endpoints.md b/ostp-wiki/api_endpoints.md deleted file mode 100644 index cf4e41a..0000000 --- a/ostp-wiki/api_endpoints.md +++ /dev/null @@ -1,149 +0,0 @@ -# Π‘ΠΏΡ€Π°Π²ΠΎΡ‡Π½ΠΈΠΊ API управлСния OSTP - -Π‘Π΅Ρ€Π²Π΅Ρ€ OSTP прСдоставляСт REST API для управлСния ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡΠΌΠΈ, просмотра статистики Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° ΠΈ ΠΈΠ½Ρ‚Π΅Ρ€Π°ΠΊΡ‚ΠΈΠ²Π½ΠΎΠ³ΠΎ рСдактирования ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ. - -По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ API ΡΠ»ΡƒΡˆΠ°Π΅Ρ‚ Π½Π° ΠΏΠΎΡ€Ρ‚Ρƒ `9090` (хост настраиваСтся Π² Ρ„Π°ΠΉΠ»Π΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ). - ---- - -## Авторизация - -ВсС запросы ΠΊ API (Π·Π° ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ΠΌ подписок) Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ `Authorization` с API-Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠΌ (Ссли Ρ‚ΠΎΠΊΠ΅Π½ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ Π² ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠΌ Ρ„Π°ΠΉΠ»Π΅): - -```http -Authorization: Bearer <ваш_api_Ρ‚ΠΎΠΊΠ΅Π½> -``` - -Или Π² ΡƒΠΏΡ€ΠΎΡ‰Π΅Π½Π½ΠΎΠΌ Π²ΠΈΠ΄Π΅: -```http -Authorization: <ваш_api_Ρ‚ΠΎΠΊΠ΅Π½> -``` - ---- - -## Π€ΠΎΡ€ΠΌΠ°Ρ‚ ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ² - -ВсС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ API Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°ΡŽΡ‚ΡΡ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ JSON ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅ΠΉ структуры: - -```json -{ - "ok": true, - "data": ..., - "error": null -} -``` - -Π’ случаС ошибки: -```json -{ - "ok": false, - "data": null, - "error": "ОписаниС ошибки" -} -``` - ---- - -## Бписок эндпоинтов - -### 1. Бтатус сСрвСра -Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ Π²Π΅Ρ€ΡΠΈΡŽ, Π°ΠΏΡ‚Π°ΠΉΠΌ ΠΈ количСство ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ. - -* **URL**: `/api/server/status` -* **ΠœΠ΅Ρ‚ΠΎΠ΄**: `GET` -* **Π€ΠΎΡ€ΠΌΠ°Ρ‚ `data`**: - ```json - { - "version": "0.2.30", - "uptime_seconds": 12053, - "active_users": 2, - "total_users": 5 - } - ``` - -### 2. ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ ΠΊΠΎΠ½Ρ„ΠΈΠ³Π° -Π—Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Π΅Ρ‚ ΠΏΠΎΠ»Π½ΠΎΠ΅ содСрТимоС Ρ„Π°ΠΉΠ»Π° `config.json` с ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ΠΌ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠ΅Π² для прямой ΠΌΠΎΠ΄ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ. - -* **URL**: `/api/server/config` -* **ΠœΠ΅Ρ‚ΠΎΠ΄**: `GET` -* **Π€ΠΎΡ€ΠΌΠ°Ρ‚ `data`**: ΠŸΠΎΠ»Π½Ρ‹ΠΉ JSON-ΠΊΠΎΠ½Ρ„ΠΈΠ³ сСрвСра. - -### 3. ОбновлСниС ΠΊΠΎΠ½Ρ„ΠΈΠ³Π° -ЗаписываСт Π½ΠΎΠ²Ρ‹ΠΉ JSON ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ сСрвСра Π² Ρ„Π°ΠΉΠ» `config.json` Π½Π° дискС. Π­Ρ‚ΠΎ автоматичСски Π²Ρ‹Π·Ρ‹Π²Π°Π΅Ρ‚ **hot-reload** ядра (ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ ΠΊΠ»ΡŽΡ‡Π΅ΠΉ доступа ΠΈ Π»ΠΈΠΌΠΈΡ‚ΠΎΠ²). - -* **URL**: `/api/server/config` -* **ΠœΠ΅Ρ‚ΠΎΠ΄**: `PUT` -* **Π’Π΅Π»ΠΎ запроса**: JSON Π½ΠΎΠ²ΠΎΠ³ΠΎ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π°. -* **Π€ΠΎΡ€ΠΌΠ°Ρ‚ `data`**: `true` Π² случаС ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎΠ³ΠΎ сохранСния. - -### 4. Бписок ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠ² ΠΈ ΠΈΡ… статистики -Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ список всСх зарСгистрированных ΠΊΠ»ΡŽΡ‡Π΅ΠΉ доступа с ΠΈΡ… Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΎΠΉ, скачиваниСм, Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ сСссиями ΠΈ статусом ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ. - -* **URL**: `/api/users` -* **ΠœΠ΅Ρ‚ΠΎΠ΄**: `GET` -* **Π€ΠΎΡ€ΠΌΠ°Ρ‚ `data`**: - ```json - [ - { - "access_key": "ostp_key_sample1", - "bytes_up": 2405020, - "bytes_down": 491029402, - "connections": 2, - "limit_bytes": 10737418240, - "online": true, - "name": "Ноутбук" - } - ] - ``` - -### 5. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° -Π“Π΅Π½Π΅Ρ€ΠΈΡ€ΡƒΠ΅Ρ‚ Π½ΠΎΠ²Ρ‹ΠΉ ΠΊΠ»ΡŽΡ‡ доступа (ΠΈΠ»ΠΈ рСгистрируСт ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΉ). - -* **URL**: `/api/users` -* **ΠœΠ΅Ρ‚ΠΎΠ΄**: `POST` -* **Π’Π΅Π»ΠΎ запроса**: - ```json - { - "access_key": "my_custom_key_optional", - "name": "Имя ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°", - "limit_bytes": 50000000000 - } - ``` -* **Π€ΠΎΡ€ΠΌΠ°Ρ‚ `data`**: Π‘Ρ‚Ρ€ΠΎΠΊΠ° созданного ΠΊΠ»ΡŽΡ‡Π° доступа. - -### 6. Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° -ΠžΡ‚Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΊΠ»ΡŽΡ‡ доступа ΠΈ сбрасываСт всС связанныС Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Π΅ сСссии. - -* **URL**: `/api/users/:key` -* **ΠœΠ΅Ρ‚ΠΎΠ΄**: `DELETE` -* **Π€ΠΎΡ€ΠΌΠ°Ρ‚ `data`**: `"User removed"` - -### 7. ОбновлСниС ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° -Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΡƒΠ΅Ρ‚ имя ΠΈΠ»ΠΈ Π»ΠΈΠΌΠΈΡ‚ Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° для ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°. - -* **URL**: `/api/users/:key` -* **ΠœΠ΅Ρ‚ΠΎΠ΄**: `PUT` -* **Π’Π΅Π»ΠΎ запроса**: - ```json - { - "name": "НовоС имя", - "limit_bytes": 100000000000 - } - ``` -* **Π€ΠΎΡ€ΠΌΠ°Ρ‚ `data`**: `"User updated"` - -### 8. Бброс счСтчиков Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° -ΠžΠ±Π½ΡƒΠ»ΡΠ΅Ρ‚ показания Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈ скачивания для ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠ³ΠΎ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ. - -* **URL**: `/api/users/{key}/reset` -* **ΠœΠ΅Ρ‚ΠΎΠ΄**: `POST` -* **Π€ΠΎΡ€ΠΌΠ°Ρ‚ `data`**: `true` - -### 9. Бсылка подписки ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° -Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ссылку подписки ΠΈΠ»ΠΈ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» для ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°. Авторизация ΠΏΠΎ Bearer-Ρ‚ΠΎΠΊΠ΅Π½Ρƒ **Π½Π΅ трСбуСтся** (ΠΊΠ»ΡŽΡ‡ авторизуСтся сам Ρ‡Π΅Ρ€Π΅Π· URL). - -* **URL**: `/api/subscribe/:key` -* **ΠœΠ΅Ρ‚ΠΎΠ΄**: `GET` -* **Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ**: - - `Accept: text/plain` -> Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Ρ‚Π΅ΠΊΡΡ‚ΠΎΠ²ΡƒΡŽ ссылку `ostp://@:?...` - - `Accept: application/json` -> Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ΠΏΠΎΠ»Π½Ρ‹ΠΉ клиСнтский JSON-ΠΊΠΎΠ½Ρ„ΠΈΠ³. diff --git a/ostp-wiki/configuration_guide.md b/ostp-wiki/configuration_guide.md deleted file mode 100644 index 2da282a..0000000 --- a/ostp-wiki/configuration_guide.md +++ /dev/null @@ -1,125 +0,0 @@ -# Руководство ΠΏΠΎ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ OSTP (`config.json`) - -Π€Π°ΠΉΠ» `config.json` являСтся основным ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹ΠΌ Ρ„Π°ΠΉΠ»ΠΎΠΌ для сСрвСра, ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° ΠΈ Ρ€Π΅Π»Π΅. - -НиТС ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅Π½ΠΎ ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС структуры для Ρ€Π΅ΠΆΠΈΠΌΠ° Ρ€Π°Π±ΠΎΡ‚Ρ‹ **Server**. - ---- - -## ΠŸΠΎΠ»Π½Ρ‹ΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ - -```json -{ - "mode": "server", - "log_level": "info", - "listen": "0.0.0.0:50000", - "access_keys": [ - "some_simple_key", - { - "access_key": "detailed_key_with_limit", - "name": "Π Π°Π±ΠΎΡ‡ΠΈΠΉ Ноутбук", - "limit_bytes": 107374182400 - } - ], - "api": { - "enabled": true, - "bind": "127.0.0.1:9090", - "token": "7a3f8b2c4d9e0f1a2b3c4d5e6f7a8b9c" - }, - "fallback": { - "enabled": false, - "listen": "0.0.0.0:443", - "target": "127.0.0.1:8080" - }, - "reality": { - "enabled": false, - "dest": "www.microsoft.com:443", - "private_key": "...", - "pbk": "...", - "sid": "...", - "sni_list": ["www.microsoft.com"] - }, - "outbound": { - "enabled": false, - "protocol": "socks5", - "address": "127.0.0.1", - "port": 9050, - "default_action": "proxy", - "rules": [ - { - "domain_suffix": [".onion"], - "action": "proxy" - } - ] - }, - "debug": false -} -``` - ---- - -## ОписаниС Ρ€Π°Π·Π΄Π΅Π»ΠΎΠ² ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ - -### 1. ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ -- **`mode`** (строка): Π Π΅ΠΆΠΈΠΌ Ρ€Π°Π±ΠΎΡ‚Ρ‹. Π’ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Π΅ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹: `"server"`, `"client"`, `"relay"`. -- **`log_level`** (строка): Π£Ρ€ΠΎΠ²Π΅Π½ΡŒ логирования. Π’Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹: `"debug"`, `"info"`, `"warn"`, `"error"`. -- **`listen`** (строка ΠΈΠ»ΠΈ массив строк): ΠŸΠΎΡ€Ρ‚ ΠΈ интСрфСйсы, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… сСрвСр ΡΠ»ΡƒΡˆΠ°Π΅Ρ‚ входящиС UDP (ΠΈ ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ TCP/UoT) соСдинСния. ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹: - - `"0.0.0.0:50000"` (всС IPv4 интСрфСйсы) - - `["0.0.0.0:50000", "[::]:50000"]` (ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° IPv4 ΠΈ IPv6 ΠΎΠ΄Π½ΠΎΠ²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎ) -- **`debug`** (логичСский): Π’ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ ΠΎΡ‚Π»Π°Π΄ΠΎΡ‡Π½ΠΎΠ΅ Π»ΠΎΠ³ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»Π°. - ---- - -### 2. ΠšΠ»ΡŽΡ‡ΠΈ доступа (`access_keys`) -Π Π°Π·Π΄Π΅Π» содСрТит массив ΠΊΠ»ΡŽΡ‡Π΅ΠΉ доступа. ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ΡΡ Π΄Π²Π° Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° записи (для ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎΠΉ совмСстимости): -1. **ΠŸΡ€ΠΎΡΡ‚Π°Ρ строка**: ВСкст ΠΊΠ»ΡŽΡ‡Π° доступа. Π›ΠΈΠΌΠΈΡ‚ Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° отсутствуСт. - ```json - "my_secure_key" - ``` -2. **ΠžΠ±ΡŠΠ΅ΠΊΡ‚ с ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹ΠΌΠΈ**: - - `access_key` (строка, ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ): ВСкст ΠΊΠ»ΡŽΡ‡Π° для ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ. - - `name` (строка, ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ): Π§Π΅Π»ΠΎΠ²Π΅ΠΊΠΎΡ‡ΠΈΡ‚Π°Π΅ΠΌΠΎΠ΅ описаниС ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°. - - `limit_bytes` (число, ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ): Π›ΠΈΠΌΠΈΡ‚ Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° Π² Π±Π°ΠΉΡ‚Π°Ρ… (Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° + скачиваниС). - -ΠŸΡ€ΠΈ достиТСнии `limit_bytes` сСссия ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° Π½Π΅ΠΌΠ΅Π΄Π»Π΅Π½Π½ΠΎ сбрасываСтся ΠΈ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ блокируСтся Π΄ΠΎ обнулСния счСтчика ΠΈΠ»ΠΈ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ Π»ΠΈΠΌΠΈΡ‚Π°. - ---- - -### 3. REST API УправлСния (`api`) -Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ½ΠΎΠ³ΠΎ управлСния сСрвСром ΠΈ ΠΈΠ½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΠΈ с внСшними панСлями. -- **`enabled`** (логичСский): Π’ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ встроСнного Π²Π΅Π±-сСрвСра API. -- **`bind`** (строка): Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ ΠΈ ΠΏΠΎΡ€Ρ‚ для ΠΏΡ€ΠΎΡΠ»ΡƒΡˆΠΈΠ²Π°Π½ΠΈΡ (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `"127.0.0.1:9090"`). -- **`token`** (строка): Bearer-Ρ‚ΠΎΠΊΠ΅Π½ для Π°Π²Ρ‚ΠΎΡ€ΠΈΠ·Π°Ρ†ΠΈΠΈ администратора. АвтоматичСски гСнСрируСтся сСрвСром ΠΏΡ€ΠΈ ΠΊΠΎΠΌΠ°Π½Π΄Π΅ `ostp --init server`. - ---- - -### 4. ВстроСнный TCP Fallback прокси (`fallback`) -ΠŸΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ‚ ΠΌΠ°ΡΠΊΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΠΎΡ€Ρ‚ ΠΏΠΎΠ΄ Π²Π΅Π±-сСрвСр ΠΏΡ€ΠΈ сканировании Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ DPI-Π·ΠΎΠ½Π΄Π°ΠΌΠΈ. -- **`enabled`** (логичСский): Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ проксированиС TCP. -- **`listen`** (строка): ΠŸΠΎΡ€Ρ‚ ΠΏΡ€ΠΎΡΠ»ΡƒΡˆΠΈΠ²Π°Π½ΠΈΡ TCP/TLS (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `"0.0.0.0:443"`). -- **`target`** (строка): Π›ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ Π²Π΅Π±-сСрвСр (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `"127.0.0.1:8080"` Π½Π° nginx/caddy), ΠΊΡƒΠ΄Π° Π±ΡƒΠ΄ΡƒΡ‚ ΠΏΠ΅Ρ€Π΅ΡΡ‹Π»Π°Ρ‚ΡŒΡΡ всС ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹Π΅ запросы (Π½Π΅-OSTP Ρ‚Ρ€Π°Ρ„ΠΈΠΊ). - ---- - -### 5. Reality ΠœΠ°ΡΠΊΠΈΡ€ΠΎΠ²ΠΊΠ° (`reality`) -Π Π΅Π°Π»ΠΈΠ·ΡƒΠ΅Ρ‚ ΡΠΏΠ΅Ρ†ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΡŽ XTLS-Reality для бСсшовной маскировки Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° ΠΏΠΎΠ΄ Π»Π΅Π³ΠΈΡ‚ΠΈΠΌΠ½Ρ‹ΠΉ TLS-сСрвСр. -- **`enabled`** (логичСский): Π’ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ маскировки. -- **`dest`** (строка): Π¦Π΅Π»Π΅Π²ΠΎΠΉ Π΄ΠΎΠΌΠ΅Π½ маскировки (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `"www.microsoft.com:443"`). -- **`private_key`** (строка): ΠŸΡ€ΠΈΠ²Π°Ρ‚Π½Ρ‹ΠΉ ΠΊΠ»ΡŽΡ‡ Reality сСрвСра (X25519). -- **`pbk`** (строка): ΠŸΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹ΠΉ ΠΊΠ»ΡŽΡ‡ Reality сСрвСра. -- **`sid`** (строка, 8 Π±Π°ΠΉΡ‚ hex): Π˜Π΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ сСссии. -- **`sni_list`** (массив строк): Π Π°Π·Ρ€Π΅ΡˆΠ΅Π½Π½Ρ‹Π΅ SNI Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ ΠΎΡ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠ². - ---- - -### 6. ΠŸΡ€Π°Π²ΠΈΠ»Π° ΠΌΠ°Ρ€ΡˆΡ€ΡƒΡ‚ΠΈΠ·Π°Ρ†ΠΈΠΈ (`outbound`) -ΠŸΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ‚ ΠΏΠ΅Ρ€Π΅ΡΡ‹Π»Π°Ρ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ исходящСго Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠ² Ρ‡Π΅Ρ€Π΅Π· прокси-сСрвСр (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, SOCKS5/TOR). -- **`enabled`** (логичСский): Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ΠΈΡΡ…ΠΎΠ΄ΡΡ‰ΡƒΡŽ ΠΌΠ°Ρ€ΡˆΡ€ΡƒΡ‚ΠΈΠ·Π°Ρ†ΠΈΡŽ. -- **`protocol`** (строка): ΠŸΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ» прокси. На Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ поддСрТиваСтся `"socks5"`. -- **`address`** (строка): Π₯ост прокси-сСрвСра. -- **`port`** (число): ΠŸΠΎΡ€Ρ‚ прокси-сСрвСра. -- **`default_action`** (строка): ДСйствиС для Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ°, Π½Π΅ попавшСго ΠΏΠΎΠ΄ ΠΏΡ€Π°Π²ΠΈΠ»Π°. Π’Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹: `"direct"` (Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ с сСрвСра) ΠΈΠ»ΠΈ `"proxy"` (Ρ‡Π΅Ρ€Π΅Π· прокси). -- **`rules`** (массив ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ²): Бписок ΠΏΡ€Π°Π²ΠΈΠ» пСрСнаправлСния: - - `domain_suffix` (массив строк): Π€ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΡ ΠΏΠΎ суффиксу Π΄ΠΎΠΌΠ΅Π½Π°. - - `ip_cidr` (массив строк): Π€ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΡ ΠΏΠΎ IP подсСтям. - - `action` (строка): ДСйствиС ΠΏΡ€ΠΈ совпадСнии (`"direct"` ΠΈΠ»ΠΈ `"proxy"`). diff --git a/ostp.wiki b/ostp.wiki new file mode 160000 index 0000000..43b4935 --- /dev/null +++ b/ostp.wiki @@ -0,0 +1 @@ +Subproject commit 43b4935fd2addc284a5ae8719824652f9063b95d diff --git a/ostp/src/main.rs b/ostp/src/main.rs index cc68adc..f229b8d 100644 --- a/ostp/src/main.rs +++ b/ostp/src/main.rs @@ -127,7 +127,7 @@ fn parse_ostp_link(link: &str) -> Result { } Ok(serde_json::json!({ - "version": "0.3.1", + "version": "{}", "log": { "level": "info" }, @@ -700,7 +700,7 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> { let client_json = serde_json::json!({ "mode": "client", - "version": "0.3.1", + "version": "{}", "log": { "level": "info" }, @@ -802,7 +802,7 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> { let port: u16 = port_str.parse().unwrap_or(50000); let server_json = serde_json::json!({ "mode": "server", - "version": "0.3.1", + "version": "{}", "log": { "level": "info" }, @@ -921,7 +921,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", + "version": "{}", "log": { "level": "info" }, @@ -1006,7 +1006,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", + "version": "{}", "log": { "level": "info" }, @@ -1120,6 +1120,19 @@ async fn run_app() -> Result<()> { return cmd_migrate(&args.config); } + if args.config.exists() && !args.uninstall && !args.update { + if let Ok(config_content) = fs::read_to_string(&args.config) { + let mut stripped = json_comments::StripComments::new(config_content.as_bytes()); + if let Ok(raw_json) = serde_json::from_reader::<_, serde_json::Value>(&mut stripped) { + if raw_json.get("version").and_then(|v| v.as_str()) != Some(env!("CARGO_PKG_VERSION")) { + println!("{} Outdated configuration format detected.", "[ostp]".yellow().bold()); + println!("{} Please run '{}' to update your configuration to the latest modular format.", "[ostp]".yellow().bold(), "ostp --migrate".green()); + std::process::exit(1); + } + } + } + } + // ── Setup wizard: explicit flag or first-time (no config) ──────── if args.setup { return run_setup_wizard(&args.config); @@ -1322,6 +1335,9 @@ async fn run_app() -> Result<()> { ostp_server::config::ServerInbound::Api { listen, port, .. } => { println!(" Inbound API: {}:{}", listen.cyan(), port.to_string().cyan()); } + ostp_server::config::ServerInbound::Dns { listen, .. } => { + println!(" Inbound DNS Tunnel: {}", listen.cyan()); + } } } println!(" Access keys: {}", keys_count.to_string().yellow()); @@ -1376,94 +1392,131 @@ async fn run_app() -> Result<()> { if let Some(ref mode_str) = args.init { let is_server = mode_str == "server"; let key = generate_secure_key("hex"); - let content = if is_server { + let dns_pub = generate_secure_key("base64"); + let dns_priv = generate_secure_key("base64"); + let content = if is_server { format!(r#"{{ - // OSTP Configuration v0.3.1 - // DO NOT EDIT THIS COMMENT - Migrator relies on it - "version": "0.3.1", + // OSTP Server Configuration + "version": "{}", "mode": "server", "log": {{ + // Log levels: trace, debug, info, warn, error "level": "info" }}, - - // The address and port the server listens on for incoming OSTP connections. - "listen": "0.0.0.0:50000", - - // List of valid keys. Clients must use one of these to connect. - "access_keys": [ - "{}" + "inbounds": [ + {{ + // Primary OSTP protocol listener + "protocol": "ostp", + "listen": "0.0.0.0", + "port": 50000, + "users": [ + {{ + // Generated access key for the first client + "key": "{}" + }} + ], + "fallback": {{ + // Fallback protection: redirects unauthorized probes to a real website + "enabled": false, + "listen": "0.0.0.0:443", + "target": "127.0.0.1:8080" + }} + }}, + {{ + // Web Administration API + "protocol": "api", + "listen": "127.0.0.1", + "port": 9090, + "token": "YOUR_SECRET_TOKEN", + "webpath": "/admin", + "username": "admin", + "password_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }}, + {{ + // DNS Tunnel Inbound + // [WARNING] This is a last-resort transport via public DNS. + // It requires a dedicated registered domain with NS records pointing to this server. + // Full setup guide: https://github.com/ospab/ostp/wiki/DNS-Tunneling + "protocol": "dns", + "tag": "dns-tunnel", + "listen": "0.0.0.0:53", + "domain": "tunnel.example.com", + "pubkey": "{dns_pub}", + "privkey": "{dns_priv}" + }} ], - - // Optional proxy for outbound traffic. - "outbound": {{ - "enabled": false, - "protocol": "socks5", - "address": "127.0.0.1", - "port": 9050, - "default_action": "proxy", + "outbounds": [ + {{ + // Example local SOCKS5 proxy (e.g. for Tor network) + "protocol": "socks5", + "tag": "socks5-local", + "server": "127.0.0.1", + "port": 9050 + }}, + {{ + // Default direct internet access + "protocol": "direct", + "tag": "direct" + }}, + {{ + // Blackhole for blocked connections + "protocol": "block", + "tag": "block" + }} + ], + "routing": {{ + // Rule-based routing of client traffic "rules": [ {{ "domain_suffix": [".onion"], - "action": "proxy" + "outbound": "socks5-local" }} - ] + ], + // If no rules match, use the default outbound + "default_outbound": "direct" }}, - - // Fallback TCP proxy: unrecognized connections are proxied to a web server (anti-DPI). - "fallback": {{ - "enabled": false, - "listen": "0.0.0.0:443", - // Target web server (e.g., local nginx or caddy) - "target": "127.0.0.1:8080" - }}, - - "debug": false, - - // [WARNING] This is a last-resort transport via public DNS. - // It requires a dedicated registered domain with NS records pointing to this server. - // Full setup guide: https://github.com/ospab/ostp/wiki/DNS-Tunneling - "dns_transport": {{ - "enabled": false, - "listen": "0.0.0.0:53", - "domain": "tunnel.example.com", - "pubkey": "SERVER_PUBKEY_BASE64_HERE", - "privkey": "SERVER_PRIVKEY_BASE64_HERE" - }} -}}"#, key) + "debug": false +}}"#, env!("CARGO_PKG_VERSION"), key, dns_pub=dns_pub, dns_priv=dns_priv) } else if mode_str == "relay" { - r#"{ - // OSTP Configuration v0.3.1 + format!(r#"{{ + // OSTP Relay Configuration v0.3.5 // DO NOT EDIT THIS COMMENT - Migrator relies on it - "version": "0.3.1", + "version": "{}", "mode": "relay", - "log": { + "log": {{ + // Log levels: trace, debug, info, warn, error "level": "info" - }, + }}, + // Local port for the relay to listen on "listen": "0.0.0.0:50000", + // Upstream server details "upstream_tcp": "TARGET_SERVER_IP:50000", "upstream_udp": "TARGET_SERVER_IP:50000", + // Upstream Control Panel API for automatic key synchronization "upstream_api_url": "http://TARGET_SERVER_IP:9090", "upstream_api_token": "YOUR_API_TOKEN_HERE", "sync_interval_secs": 30, "debug": false -}"#.to_string() +}}"#, env!("CARGO_PKG_VERSION")) } else { format!(r#"{{ - // OSTP Configuration v0.3.1 + // OSTP Client Configuration // DO NOT EDIT THIS COMMENT - Migrator relies on it - "version": "0.3.1", + "version": "{}", "mode": "client", "log": {{ "level": "info" }}, "inbounds": [ {{ + // Virtual network interface for transparent proxying "type": "tun", "tag": "tun-in", "auto_route": true, "mtu": 1140 }}, {{ + // Local SOCKS5 proxy server for browser configuration "type": "local_proxy", "tag": "socks-in", "protocol": "socks", @@ -1473,6 +1526,7 @@ async fn run_app() -> Result<()> { ], "outbounds": [ {{ + // Connection to the remote OSTP server "type": "ostp", "tag": "proxy", "server": "YOUR_SERVER_IP", @@ -1504,7 +1558,7 @@ async fn run_app() -> Result<()> { ], "default_outbound": "proxy" }} -}}"#, key = key) +}}"#, env!("CARGO_PKG_VERSION"), key = key) }; if let Some(parent) = args.config.parent() { if !parent.as_os_str().is_empty() { @@ -1570,12 +1624,12 @@ async fn run_app() -> Result<()> { let mut raw_json: serde_json::Value = serde_json::from_reader(&mut stripped) .map_err(|e| anyhow!("Failed to parse config as JSON: {}", e))?; - let is_migrated = raw_json.get("version").and_then(|v| v.as_str()) == Some("0.3.1"); + let is_migrated = raw_json.get("version").and_then(|v| v.as_str()) == Some(env!("CARGO_PKG_VERSION")); if !is_migrated { let is_server = raw_json.get("listen").is_some() || raw_json.get("access_keys").is_some(); if is_server { raw_json["mode"] = serde_json::json!("server"); - raw_json["version"] = serde_json::json!("0.3.1"); + raw_json["version"] = serde_json::json!(env!("CARGO_PKG_VERSION")); if let Some(log) = raw_json.get("log_level") { raw_json["log"] = serde_json::json!({ "level": log.clone() }); } @@ -1663,6 +1717,7 @@ async fn run_app() -> Result<()> { let mut fallback_config = None; let mut host_port = ("0.0.0.0".to_string(), 50000); let mut api_config = None; + let mut dns_transport = None; for inbound in server_cfg.inbounds { match inbound { @@ -1689,6 +1744,15 @@ async fn run_app() -> Result<()> { password_hash: password_hash.unwrap_or_default(), }); } + ostp_server::config::ServerInbound::Dns { listen, domain, pubkey, privkey, .. } => { + dns_transport = Some(ostp_server::config::DnsTransportConfig { + enabled: true, + listen, + domain, + pubkey: pubkey.unwrap_or_default(), + privkey: privkey.unwrap_or_default(), + }); + } } } @@ -1735,7 +1799,7 @@ async fn run_app() -> Result<()> { host_port.0.to_string() }; - ostp_server::run_server(listen_addrs, Some(host), access_keys_meta, outbound, api_config, fallback_config, debug, dns_cfg, server_cfg.dns_transport, Some(args.config)).await?; + ostp_server::run_server(listen_addrs, Some(host), access_keys_meta, outbound, api_config, fallback_config, debug, dns_cfg, dns_transport, Some(args.config)).await?; } AppMode::Client(client_cfg) => { println!("{}", include_str!("../../docs/banner.txt").blue().bold()); @@ -1877,32 +1941,140 @@ fn cmd_migrate(config_path: &std::path::Path) -> Result<()> { let mut raw_json: serde_json::Value = serde_json::from_reader(&mut stripped) .map_err(|e| anyhow!("Failed to parse config as JSON: {}", e))?; - let is_migrated = raw_json.get("version").and_then(|v| v.as_str()) == Some("0.3.1"); + let is_migrated = raw_json.get("version").and_then(|v| v.as_str()) == Some(env!("CARGO_PKG_VERSION")); if is_migrated { - println!("{} Configuration is already up to date (v0.3.1)", "[ostp]".cyan().bold()); + println!("{} Configuration is already up to date (v0.3.5)", "[ostp]".cyan().bold()); return Ok(()); } - let is_server = raw_json.get("listen").is_some() || raw_json.get("access_keys").is_some(); + let is_server = raw_json.get("listen").is_some() || raw_json.get("access_keys").is_some() || raw_json.get("mode").and_then(|m| m.as_str()) == Some("server"); if is_server { raw_json["mode"] = serde_json::json!("server"); - raw_json["version"] = serde_json::json!("0.3.1"); + raw_json["version"] = serde_json::json!(env!("CARGO_PKG_VERSION")); if let Some(log) = raw_json.get("log_level") { raw_json["log"] = serde_json::json!({ "level": log.clone() }); } + + let mut inbounds = Vec::new(); + let mut outbounds = Vec::new(); + let mut routing = serde_json::json!({ + "rules": [], + "default_outbound": "direct" + }); + + // Migrate Ostp inbound + let listen = raw_json.get("listen").and_then(|l| l.as_str()).unwrap_or("0.0.0.0:50000"); + let parts: Vec<&str> = listen.split(':').collect(); + let host = parts.get(0).unwrap_or(&"0.0.0.0"); + let port: u16 = parts.get(1).and_then(|p| p.parse().ok()).unwrap_or(50000); + + let mut users = Vec::new(); + if let Some(keys) = raw_json.get("access_keys").and_then(|a| a.as_array()) { + for k in keys { + users.push(serde_json::json!({ + "key": k.as_str().unwrap_or("") + })); + } + } + + let mut ostp_inbound = serde_json::json!({ + "protocol": "ostp", + "listen": host, + "port": port, + "users": users + }); + + if let Some(fallback) = raw_json.get("fallback") { + ostp_inbound["fallback"] = fallback.clone(); + } + inbounds.push(ostp_inbound); + + // Migrate Api inbound + if let Some(api) = raw_json.get("api") { + let mut api_inbound = api.clone(); + api_inbound["protocol"] = serde_json::json!("api"); + let bind = api.get("bind").and_then(|b| b.as_str()).unwrap_or("127.0.0.1:9090"); + let parts: Vec<&str> = bind.split(':').collect(); + api_inbound["listen"] = serde_json::json!(parts.get(0).unwrap_or(&"127.0.0.1")); + api_inbound["port"] = serde_json::json!(parts.get(1).and_then(|p| p.parse::().ok()).unwrap_or(9090)); + inbounds.push(api_inbound); + } + + // Migrate Outbound + outbounds.push(serde_json::json!({ + "protocol": "direct", + "tag": "direct" + })); + outbounds.push(serde_json::json!({ + "protocol": "block", + "tag": "block" + })); + + if let Some(ob) = raw_json.get("outbound") { + if ob.get("enabled").and_then(|e| e.as_bool()).unwrap_or(false) { + let tag = "socks5-legacy"; + let mut socks = serde_json::json!({ + "protocol": "socks5", + "tag": tag, + "server": ob.get("address").and_then(|a| a.as_str()).unwrap_or("127.0.0.1"), + "port": ob.get("port").and_then(|p| p.as_u64()).unwrap_or(9050) + }); + outbounds.push(socks); + + if let Some(rules) = ob.get("rules").and_then(|r| r.as_array()) { + let mut new_rules = Vec::new(); + for rule in rules { + let mut new_rule = rule.clone(); + new_rule["outbound"] = serde_json::json!(tag); + new_rules.push(new_rule); + } + routing["rules"] = serde_json::json!(new_rules); + } + + let default_action = ob.get("default_action").and_then(|a| a.as_str()).unwrap_or("proxy"); + if default_action == "proxy" { + routing["default_outbound"] = serde_json::json!(tag); + } else if default_action == "block" { + routing["default_outbound"] = serde_json::json!("block"); + } + } + } + + // DNS migrate + if let Some(dns) = raw_json.get("dns_transport") { + let mut dns_inbound = dns.clone(); + dns_inbound["protocol"] = serde_json::json!("dns"); + dns_inbound["tag"] = serde_json::json!("dns-tunnel"); + inbounds.push(dns_inbound); + } + + raw_json["inbounds"] = serde_json::json!(inbounds); + raw_json["outbounds"] = serde_json::json!(outbounds); + raw_json["routing"] = routing; + + // Remove legacy fields + let obj = raw_json.as_object_mut().unwrap(); + obj.remove("listen"); + obj.remove("access_keys"); + obj.remove("fallback"); + obj.remove("api"); + obj.remove("outbound"); + obj.remove("log_level"); + obj.remove("dns_transport"); + println!("{} Detected Server configuration.", "[ostp]".cyan().bold()); } else { println!("{} Detected Client configuration.", "[ostp]".cyan().bold()); - let (migrated, _) = ostp_client::config::ClientConfig::migrate_json(raw_json); + let (migrated, _) = ostp_client::config::ClientConfig::migrate_json(raw_json.clone()); raw_json = migrated; raw_json["mode"] = serde_json::json!("client"); + raw_json["version"] = serde_json::json!(env!("CARGO_PKG_VERSION")); } let serialized = serde_json::to_string_pretty(&raw_json)?; - let header = "// OSTP Configuration v0.3.1\n// DO NOT EDIT THIS COMMENT - Migrator relies on it\n"; - let final_content = format!("{}{}", header, serialized); + let final_content = format!("{}", serialized); fs::write(config_path, final_content)?; - println!("{} Successfully migrated configuration to v0.3.1!", "[ostp]".green().bold()); + println!("{} Successfully migrated configuration to v0.3.5!", "[ostp]".green().bold()); Ok(()) } diff --git a/refactor_main_1.py b/refactor_main_1.py deleted file mode 100644 index 6476fb7..0000000 --- a/refactor_main_1.py +++ /dev/null @@ -1,261 +0,0 @@ -import os -import re - -with open('ostp/src/main.rs', 'r', encoding='utf-8') as f: - content = f.read() - -# 1. Update validation logic in `UnifiedConfig::validate` -content = content.replace(''' if let AppMode::Server(cfg) = &self.mode { - if cfg.access_keys.is_empty() { - anyhow::bail!("Server configuration must contain at least one access_key."); - } - if let Some(outbound) = &cfg.outbound { - if outbound.enabled { - if outbound.protocol != "socks5" { - anyhow::bail!("Only SOCKS5 is currently supported for outbound connections."); - } - } - } - }''', ''' if let AppMode::Server(cfg) = &self.mode { - let mut has_ostp = false; - for inbound in &cfg.inbounds { - if let ostp_server::config::ServerInbound::Ostp { users, .. } = inbound { - has_ostp = true; - if users.is_empty() { - anyhow::bail!("Ostp inbound must contain at least one user."); - } - } - } - if !has_ostp { - anyhow::bail!("Server configuration must contain at least one Ostp inbound."); - } - }''') - -# 2. Update `cmd_add_user` -# Inside cmd_add_user, we need to read json, append user to the Ostp inbound -old_add_user = ''' let mut config: serde_json::Value = serde_json::from_str(&content)?; - if let Some(keys) = config.get_mut("access_keys").and_then(|k| k.as_array_mut()) { - if let Some(meta) = user_meta { - let mut obj = serde_json::Map::new(); - obj.insert("key".to_string(), serde_json::Value::String(key_to_add.clone())); - if let Some(name) = meta.name { - obj.insert("name".to_string(), serde_json::Value::String(name)); - } - if let Some(limit) = meta.limit_bytes { - obj.insert("limit_bytes".to_string(), serde_json::Value::Number(limit.into())); - } - keys.push(serde_json::Value::Object(obj)); - } else { - keys.push(serde_json::Value::String(key_to_add.clone())); - } - } else { - anyhow::bail!("Invalid or missing access_keys array in config.json"); - } - wizard_save_config(config_path, &config)?;''' - -new_add_user = ''' let mut config: serde_json::Value = serde_json::from_str(&content)?; - let mut added = false; - if let Some(inbounds) = config.get_mut("inbounds").and_then(|i| i.as_array_mut()) { - for inbound in inbounds.iter_mut() { - if inbound.get("type").and_then(|t| t.as_str()) == Some("ostp") { - if let Some(users) = inbound.get_mut("users").and_then(|u| u.as_array_mut()) { - if let Some(meta) = &user_meta { - let mut obj = serde_json::Map::new(); - obj.insert("key".to_string(), serde_json::Value::String(key_to_add.clone())); - if let Some(name) = &meta.name { - obj.insert("name".to_string(), serde_json::Value::String(name.clone())); - } - if let Some(limit) = meta.limit_bytes { - obj.insert("limit_bytes".to_string(), serde_json::Value::Number(limit.into())); - } - users.push(serde_json::Value::Object(obj)); - } else { - users.push(serde_json::Value::String(key_to_add.clone())); - } - added = true; - break; - } - } - } - } - if !added { - anyhow::bail!("Could not find Ostp inbound with users array in config.json"); - } - wizard_save_config(config_path, &config)?;''' -content = content.replace(old_add_user, new_add_user) - -# 3. Update JSON template in cmd_add_user (where server_json is generated if config doesn't exist) -old_server_json_1 = ''' let server_json = serde_json::json!({ - "mode": "server", - "version": "0.3.1", - "log": { - "level": "info" - }, - "listen": listen, - "access_keys": access_keys, - "outbound": { - "enabled": false, - "protocol": "socks5", - "address": "127.0.0.1", - "port": 9050, - "default_action": "proxy", - "rules": [] - }, - "fallback": { "enabled": false, "listen": "0.0.0.0:443", "target": "127.0.0.1:8080" }, - "debug": false - });''' - -new_server_json_1 = ''' let server_json = serde_json::json!({ - "mode": "server", - "version": "0.3.1", - "log": { - "level": "info" - }, - "inbounds": [ - { - "type": "ostp", - "tag": "ostp-in", - "listen": "0.0.0.0", - "port": 50000, - "users": access_keys - } - ], - "outbounds": [ - { - "type": "direct", - "tag": "direct" - } - ] - });''' -content = content.replace(old_server_json_1, new_server_json_1) - -# 4. Update JSON template in cmd_run_relay_wizard -old_server_json_2 = ''' let server_json = serde_json::json!({ - "mode": "server", - "version": "0.3.1", - "log": { - "level": "info" - }, - "listen": listen, - "access_keys": access_keys, - "outbound": { - "enabled": false, - "protocol": "socks5", - "address": "127.0.0.1", - "port": 9050, - "default_action": "proxy", - "rules": [] - }, - "api": { - "enabled": true, - "bind": panel_bind, - "webpath": webpath, - "username": username, - "password_hash": pass_hash - }, - "fallback": { "enabled": false, "listen": "0.0.0.0:443", "target": "127.0.0.1:8080" }, - "debug": false, - "license_key": license_key - });''' - -new_server_json_2 = ''' let server_json = serde_json::json!({ - "mode": "server", - "version": "0.3.1", - "log": { - "level": "info" - }, - "inbounds": [ - { - "type": "ostp", - "tag": "ostp-in", - "listen": "0.0.0.0", - "port": 50000, - "users": access_keys - }, - { - "type": "api", - "tag": "api-in", - "listen": "0.0.0.0", - "port": panel_port.parse::().unwrap_or(9090), - "webpath": webpath, - "username": username, - "password_hash": pass_hash - } - ], - "outbounds": [ - { - "type": "direct", - "tag": "direct" - } - ], - "license_key": license_key - });''' -content = content.replace(old_server_json_2, new_server_json_2) - -# 5. Fix configuration info display -old_info = ''' let mut has_outbound = false; - println!("{} {} server:", "[ostp]".cyan().bold(), "OSTP".green().bold()); - println!(" Listen: {:?}", s.listen.primary().as_str().cyan()); - println!(" Access keys: {}", s.access_keys.len().to_string().yellow()); - if let Some(api) = &s.api { - println!(" Control Panel API: {} (bind: {})", - if api.enabled { "enabled" } else { "disabled" }, - api.bind.as_str()); - } - if let Some(outbound) = &s.outbound { - if outbound.enabled { - println!(" Outbound Proxy: SOCKS5 {} (default_action: {})", outbound.address.cyan(), outbound.default_action.as_deref().unwrap_or("proxy").cyan()); - has_outbound = true; - } - } - if let Some(fb) = &s.fallback { - if fb.enabled { - println!(" Anti-DPI Fallback: Target {} (bind: {})", fb.target.cyan(), fb.listen.cyan()); - } - } - if let Some(dns) = &s.dns { - println!(" DNS Proxy: Listen {}", dns.listen.as_deref().unwrap_or("0.0.0.0:53").cyan()); - } - if !has_outbound { - println!(" Outbound Proxy: disabled"); - }''' - -new_info = ''' println!("{} {} server:", "[ostp]".cyan().bold(), "OSTP".green().bold()); - let mut keys_count = 0; - let mut has_outbound = false; - for inbound in &s.inbounds { - match inbound { - ostp_server::config::ServerInbound::Ostp { listen, port, users, fallback, .. } => { - println!(" Inbound OSTP: {}:{}", listen.cyan(), port.to_string().cyan()); - keys_count += users.len(); - if let Some(fb) = fallback { - if fb.enabled { - println!(" Fallback: -> {}", fb.target.cyan()); - } - } - } - ostp_server::config::ServerInbound::Api { listen, port, .. } => { - println!(" Inbound API: {}:{}", listen.cyan(), port.to_string().cyan()); - } - } - } - println!(" Access keys: {}", keys_count.to_string().yellow()); - - for ob in &s.outbounds { - if let ostp_server::config::ServerOutbound::Socks { server, port, .. } = ob { - println!(" Outbound Proxy: SOCKS5 {}:{}", server.cyan(), port.to_string().cyan()); - has_outbound = true; - } - } - - if let Some(dns) = &s.dns { - println!(" DNS Proxy: Listen {}", dns.listen.as_deref().unwrap_or("0.0.0.0:53").cyan()); - } - if !has_outbound { - println!(" Outbound Proxy: disabled"); - }''' -content = content.replace(old_info, new_info) - - -with open('ostp/src/main.rs', 'w', encoding='utf-8') as f: - f.write(content) diff --git a/refactor_main_2.py b/refactor_main_2.py deleted file mode 100644 index 1a3e225..0000000 --- a/refactor_main_2.py +++ /dev/null @@ -1,226 +0,0 @@ -import os - -with open('ostp/src/main.rs', 'r', encoding='utf-8') as f: - content = f.read() - -# Replace run_app Server parsing -old_run_app = ''' if let Some(cmd) = matches.subcommand_matches("user") { - if let Some(key) = cmd.get_one::("add") { - let limit_str = cmd.get_one::("limit"); - let name = cmd.get_one::("name").cloned(); - let limit_bytes = limit_str.map(|s| s.parse::().unwrap_or(0) * 1024 * 1024 * 1024); - let meta = ostp_server::api::UserMeta { name, limit_bytes }; - cmd_add_user(&args.config, key, Some(meta))?; - } else if let Some(key) = cmd.get_one::("delete") { - cmd_delete_user(&args.config, key)?; - } else if cmd.get_flag("list") { - cmd_list_users(&args.config, server_cfg)?; - } - return Ok(()); - } - - if args.share_link { - let host = server_cfg.listen.host(); - let port = server_cfg.listen.port(); - - let host = if host == "0.0.0.0" { - println!("[ostp] Server listens on 0.0.0.0. Detecting public IP..."); - get_or_ask_public_ip(&args.config) - } else { - host.to_string() - }; - - for (idx, key) in server_cfg.access_keys.iter().enumerate() { - let meta_name = key.name().unwrap_or_else(|| format!("user{}", idx + 1)); - let meta_name_encoded = urlencoding::encode(&meta_name); - - let mut link = format!("ostp://{}@{}:{}", key.key(), host, port); - link.push_str(&format!("?name={}", meta_name_encoded)); - - if let Some(transport) = &server_cfg.transport { - if let Some(mode) = &transport.mode { - link.push_str(&format!("&mode={}", mode)); - } - } - - println!("Client #{}:", idx + 1); - println!(" {}", link.cyan().bold()); - } - return Ok(()); - } - - let host = server_cfg.listen.host(); - let host = if host == "0.0.0.0" { - detect_local_public_ip().unwrap_or_else(|| "127.0.0.1".to_string()) - } else { - host.to_string() - }; - - let listen_addrs = server_cfg.listen.addresses(); - - // Map JSON Outbound to core OutboundConfig - let outbound = server_cfg.outbound.map(|o| ostp_server::OutboundConfig { - enabled: o.enabled, - protocol: o.protocol, - address: o.address, - port: o.port, - rules: o.rules, - default_action: o.default_action, - }); - - // Map API - let api_config = server_cfg.api.map(|a| ostp_server::ApiConfig { - enabled: a.enabled, - bind: a.bind, - token: a.token, - webpath: a.webpath, - username: a.username, - password_hash: a.password_hash, - }); - - // Map Fallback - let fallback_config = server_cfg.fallback.map(|f| ostp_server::FallbackConfig { - enabled: f.enabled, - listen: f.listen, - target: f.target, - }); - - let access_keys_meta = server_cfg.access_keys.into_iter().map(|uc| { - (uc.key(), ostp_server::api::UserMeta { - name: uc.name(), - limit_bytes: uc.limit(), - }) - }).collect(); - - let dns_cfg = server_cfg.dns; - - ostp_server::run_server(listen_addrs, Some(host), access_keys_meta, outbound, api_config, fallback_config, debug, dns_cfg, Some(args.config), server_cfg.license_key.clone()).await?;''' - -new_run_app = ''' if let Some(cmd) = matches.subcommand_matches("user") { - if let Some(key) = cmd.get_one::("add") { - let limit_str = cmd.get_one::("limit"); - let name = cmd.get_one::("name").cloned(); - let limit_bytes = limit_str.map(|s| s.parse::().unwrap_or(0) * 1024 * 1024 * 1024); - let meta = ostp_server::api::UserMeta { name, limit_bytes }; - cmd_add_user(&args.config, key, Some(meta))?; - } else if let Some(key) = cmd.get_one::("delete") { - cmd_delete_user(&args.config, key)?; - } else if cmd.get_flag("list") { - // cmd_list_users needs update - // cmd_list_users(&args.config, server_cfg)?; - } - return Ok(()); - } - - // Extract ostp inbound info - let mut listen_addrs = Vec::new(); - let mut access_keys_meta = Vec::new(); - let mut fallback_config = None; - let mut host_port = ("0.0.0.0".to_string(), 50000); - let mut transport_mode = None; - - let mut api_config = None; - - for inbound in server_cfg.inbounds { - match inbound { - ostp_server::config::ServerInbound::Ostp { listen, port, users, fallback, transport, .. } => { - listen_addrs.push(format!("{}:{}", listen, port)); - host_port = (listen, port); - for uc in users { - access_keys_meta.push((uc.key(), ostp_server::api::UserMeta { - name: uc.name(), - limit_bytes: uc.limit(), - })); - } - if fallback_config.is_none() { - fallback_config = fallback; - } - if let Some(tr) = transport { - transport_mode = tr.mode; - } - } - ostp_server::config::ServerInbound::Api { listen, port, token, webpath, username, password_hash, .. } => { - api_config = Some(ostp_server::ApiConfig { - enabled: true, - bind: format!("{}:{}", listen, port), - token, - webpath, - username, - password_hash, - }); - } - } - } - - if args.share_link { - let host = if host_port.0 == "0.0.0.0" { - println!("[ostp] Server listens on 0.0.0.0. Detecting public IP..."); - get_or_ask_public_ip(&args.config) - } else { - host_port.0.to_string() - }; - - for (idx, (key, meta)) in access_keys_meta.iter().enumerate() { - let meta_name = meta.name.clone().unwrap_or_else(|| format!("user{}", idx + 1)); - let meta_name_encoded = urlencoding::encode(&meta_name); - - let mut link = format!("ostp://{}@{}:{}", key, host, host_port.1); - link.push_str(&format!("?name={}", meta_name_encoded)); - - if let Some(mode) = &transport_mode { - link.push_str(&format!("&mode={}", mode)); - } - - println!("Client #{}:", idx + 1); - println!(" {}", link.cyan().bold()); - } - return Ok(()); - } - - let host = if host_port.0 == "0.0.0.0" { - detect_local_public_ip().unwrap_or_else(|| "127.0.0.1".to_string()) - } else { - host_port.0.to_string() - }; - - // Map JSON Outbound to core OutboundConfig - let mut outbound = None; - for ob in server_cfg.outbounds { - if let ostp_server::config::ServerOutbound::Socks { server, port, tag } = ob { - let mut rules = Vec::new(); - let mut default_action = Some("proxy".to_string()); - if let Some(routing) = &server_cfg.routing { - for rule in &routing.rules { - if rule.outbound == tag { - rules.push(ostp_server::OutboundRule { - domain_suffix: rule.domain_suffix.clone(), - ip_cidr: rule.ip_cidr.clone(), - protocol: rule.protocol.clone(), - action: Some("proxy".to_string()), - }); - } - } - if routing.default_outbound != tag { - default_action = Some("direct".to_string()); - } - } - outbound = Some(ostp_server::OutboundConfig { - enabled: true, - protocol: "socks5".to_string(), - address: server, - port, - rules, - default_action, - }); - break; // Only map the first SOCKS outbound for now - } - } - - let dns_cfg = server_cfg.dns; - - ostp_server::run_server(listen_addrs, Some(host), access_keys_meta, outbound, api_config, fallback_config, debug, dns_cfg, Some(args.config), server_cfg.license_key.clone()).await?;''' - -content = content.replace(old_run_app, new_run_app) - -with open('ostp/src/main.rs', 'w', encoding='utf-8') as f: - f.write(content) diff --git a/refactor_main_3.py b/refactor_main_3.py deleted file mode 100644 index a19c4c7..0000000 --- a/refactor_main_3.py +++ /dev/null @@ -1,81 +0,0 @@ -import os - -with open('ostp/src/main.rs', 'r', encoding='utf-8') as f: - content = f.read() - -# Fix cmd_list_users -old_list_users = '''fn cmd_list_users(config_path: &std::path::Path, server_cfg: ServerConfig) -> Result<()> { - println!("{} {} server:", "[ostp]".cyan().bold(), "OSTP".green().bold()); - println!(" Listen: {:?}", server_cfg.listen.primary().as_str().cyan()); - println!(" Access keys: {}", server_cfg.access_keys.len().to_string().yellow()); - - if server_cfg.access_keys.is_empty() { - println!(" No users found."); - return Ok(()); - } - - println!("\n Users:"); - for (idx, key) in server_cfg.access_keys.iter().enumerate() { - let name_str = if let Some(n) = key.name() { - format!(" ({})", n.green()) - } else { - "".to_string() - }; - let limit_str = if let Some(l) = key.limit() { - let l_gb = l as f64 / (1024.0 * 1024.0 * 1024.0); - format!(" [limit: {:.2} GB]", l_gb.to_string().yellow()) - } else { - "".to_string() - }; - println!(" {}. {}{}{}", idx + 1, key.key().cyan(), name_str, limit_str); - } - Ok(()) -}''' - -new_list_users = '''fn cmd_list_users(config_path: &std::path::Path, server_cfg: ServerConfig) -> Result<()> { - println!("{} {} server:", "[ostp]".cyan().bold(), "OSTP".green().bold()); - - let mut users = Vec::new(); - for inbound in server_cfg.inbounds { - if let ostp_server::config::ServerInbound::Ostp { users: u, listen, port, .. } = inbound { - println!(" Listen: {}:{}", listen.cyan(), port.to_string().cyan()); - users.extend(u); - } - } - - println!(" Access keys: {}", users.len().to_string().yellow()); - - if users.is_empty() { - println!(" No users found."); - return Ok(()); - } - - println!("\n Users:"); - for (idx, key) in users.iter().enumerate() { - let name_str = if let Some(n) = key.name() { - format!(" ({})", n.green()) - } else { - "".to_string() - }; - let limit_str = if let Some(l) = key.limit() { - let l_gb = l as f64 / (1024.0 * 1024.0 * 1024.0); - format!(" [limit: {:.2} GB]", l_gb.to_string().yellow()) - } else { - "".to_string() - }; - println!(" {}. {}{}{}", idx + 1, key.key().cyan(), name_str, limit_str); - } - Ok(()) -}''' - -content = content.replace(old_list_users, new_list_users) - -# Fix commented cmd_list_users in run_app -old_call_list = ''' // cmd_list_users needs update - // cmd_list_users(&args.config, server_cfg)?;''' -new_call_list = ''' cmd_list_users(&args.config, server_cfg)?;''' -content = content.replace(old_call_list, new_call_list) - - -with open('ostp/src/main.rs', 'w', encoding='utf-8') as f: - f.write(content) diff --git a/refactor_main_4.py b/refactor_main_4.py deleted file mode 100644 index b303fe0..0000000 --- a/refactor_main_4.py +++ /dev/null @@ -1,142 +0,0 @@ -import os - -with open('ostp/src/main.rs', 'r', encoding='utf-8') as f: - content = f.read() - -old_run_app_block = ''' AppMode::Server(server_cfg) => { - println!("{}", include_str!("../../docs/banner.txt").blue().bold()); - - let listen_addrs = server_cfg.listen.addresses(); - println!("{} Starting server on {:?}", "[ostp]".cyan().bold(), listen_addrs); - let debug = server_cfg.debug.unwrap_or(false); - let outbound = server_cfg.outbound.map(|o| ostp_server::OutboundConfig { - enabled: o.enabled, - protocol: o.protocol, - address: o.address, - port: o.port, - rules: o - .rules - .into_iter() - .map(|r| ostp_server::OutboundRule { - domain_suffix: r.domain_suffix.unwrap_or_default(), - ip_cidr: r.ip_cidr.unwrap_or_default(), - protocol: r.protocol, - action: parse_outbound_action(r.action), - }) - .collect(), - default_action: parse_outbound_action(o.default_action), - }); - let api_config = server_cfg.api.map(|a| ostp_server::ApiConfig { - enabled: a.enabled.unwrap_or(false), - bind: a.bind.unwrap_or_else(|| "127.0.0.1:9090".to_string()), - token: a.token.clone(), - webpath: a.webpath.unwrap_or_default(), - username: a.username.unwrap_or_default(), - password_hash: a.password_hash.unwrap_or_default(), - }); - let fallback_config = server_cfg.fallback.map(|f| ostp_server::FallbackConfig { - enabled: f.enabled.unwrap_or(false), - listen: f.listen.unwrap_or_else(|| "0.0.0.0:443".to_string()), - target: f.target.unwrap_or_else(|| "127.0.0.1:8080".to_string()), - }); - - let access_keys_meta = server_cfg.access_keys.into_iter().map(|uc| { - (uc.key(), ostp_server::api::UserMeta { - name: uc.name(), - limit_bytes: uc.limit(), - }) - }).collect::>(); - let host = get_or_ask_public_ip(&args.config); - // Build DNS config and set owndns flag in subscribe links if DNS enabled - let dns_cfg = server_cfg.dns; - // Pass all listen addresses for multi-listener support - ostp_server::run_server(listen_addrs, Some(host), access_keys_meta, outbound, api_config, fallback_config, debug, dns_cfg, Some(args.config), server_cfg.license_key.clone()).await?; - }''' - -new_run_app_block = ''' AppMode::Server(server_cfg) => { - println!("{}", include_str!("../../docs/banner.txt").blue().bold()); - - let mut listen_addrs = Vec::new(); - let mut access_keys_meta = Vec::new(); - let mut fallback_config = None; - let mut host_port = ("0.0.0.0".to_string(), 50000); - let mut api_config = None; - - for inbound in server_cfg.inbounds { - match inbound { - ostp_server::config::ServerInbound::Ostp { listen, port, users, fallback, .. } => { - listen_addrs.push(format!("{}:{}", listen, port)); - host_port = (listen.clone(), port); - for uc in users { - access_keys_meta.push((uc.key(), ostp_server::api::UserMeta { - name: uc.name(), - limit_bytes: uc.limit(), - })); - } - if fallback_config.is_none() { - fallback_config = fallback; - } - } - ostp_server::config::ServerInbound::Api { listen, port, token, webpath, username, password_hash, .. } => { - api_config = Some(ostp_server::ApiConfig { - enabled: true, - bind: format!("{}:{}", listen, port), - token, - webpath, - username, - password_hash, - }); - } - } - } - - println!("{} Starting server on {:?}", "[ostp]".cyan().bold(), listen_addrs); - let debug = server_cfg.debug.unwrap_or(false); - - let mut outbound = None; - for ob in server_cfg.outbounds { - if let ostp_server::config::ServerOutbound::Socks { server, port, tag } = ob { - let mut rules = Vec::new(); - let mut default_action = Some("proxy".to_string()); - if let Some(routing) = &server_cfg.routing { - for rule in &routing.rules { - if rule.outbound == tag { - rules.push(ostp_server::OutboundRule { - domain_suffix: rule.domain_suffix.clone(), - ip_cidr: rule.ip_cidr.clone(), - protocol: rule.protocol.clone(), - action: Some("proxy".to_string()), - }); - } - } - if routing.default_outbound != tag { - default_action = Some("direct".to_string()); - } - } - outbound = Some(ostp_server::OutboundConfig { - enabled: true, - protocol: "socks5".to_string(), - address: server, - port, - rules, - default_action, - }); - break; - } - } - - let dns_cfg = server_cfg.dns; - - let host = if host_port.0 == "0.0.0.0" { - detect_local_public_ip().unwrap_or_else(|| "127.0.0.1".to_string()) - } else { - host_port.0.to_string() - }; - - ostp_server::run_server(listen_addrs, Some(host), access_keys_meta, outbound, api_config, fallback_config, debug, dns_cfg, Some(args.config), server_cfg.license_key.clone()).await?; - }''' - -content = content.replace(old_run_app_block, new_run_app_block) - -with open('ostp/src/main.rs', 'w', encoding='utf-8') as f: - f.write(content)