From ba5fe7287348405bec3be6c792fcc556abe0e0e0 Mon Sep 17 00:00:00 2001 From: ospab Date: Sun, 31 May 2026 20:53:54 +0300 Subject: [PATCH] feat(cli): add --import, --proxy-env, interactive link prompt, and TUN safety guard for Linux --- ostp-client/src/tunnel/native_handler.rs | 23 ++++- ostp/src/main.rs | 110 ++++++++++++++++++++++- 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/ostp-client/src/tunnel/native_handler.rs b/ostp-client/src/tunnel/native_handler.rs index 7387045..fff860d 100644 --- a/ostp-client/src/tunnel/native_handler.rs +++ b/ostp-client/src/tunnel/native_handler.rs @@ -15,6 +15,27 @@ pub async fn run_native_tunnel( #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; + #[cfg(target_os = "linux")] + { + use std::io::{self, IsTerminal, Write}; + if io::stdout().is_terminal() { + println!("\n==================================================================="); + println!("WARNING: TUN mode will modify the system routing table."); + println!("If you are connected to a headless server via SSH, you may lose"); + println!("your connection when default routes are redirected into the tunnel."); + println!("===================================================================\n"); + print!("Are you sure you want to initialize the TUN interface? [yes/no]: "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + let ans = input.trim().to_lowercase(); + if ans != "y" && ans != "yes" { + return Err(anyhow!("TUN initialization aborted by user. Run without TUN to use as a local proxy.")); + } + } + } + let debug = config.debug; tracing::info!("Initializing NATIVE TUN tunnel (smoltcp)..."); @@ -31,7 +52,7 @@ pub async fn run_native_tunnel( .address((10, 1, 0, 2)) .netmask((255, 255, 255, 0)) .destination((10, 1, 0, 1)) - .mtu(config.ostp.mtu.saturating_sub(48).max(500) as u16) + .mtu(config.ostp.mtu as u16) .up(); #[cfg(target_os = "linux")] diff --git a/ostp/src/main.rs b/ostp/src/main.rs index cb8003a..99b1c75 100644 --- a/ostp/src/main.rs +++ b/ostp/src/main.rs @@ -46,6 +46,18 @@ struct Args { /// Update OSTP: re-run the install script to fetch and install the latest version #[arg(long)] update: bool, + + /// Import a share link (ostp://...) into the configuration file and exit + #[arg(long)] + import: Option, + + /// Output shell export commands for proxy (eval $(ostp --proxy-env)) + #[arg(long)] + proxy_env: bool, + + /// Output shell export commands to clear proxy (eval $(ostp --proxy-env-clear)) + #[arg(long)] + proxy_env_clear: bool, } fn parse_ostp_link(link: &str) -> Result { @@ -512,6 +524,35 @@ async fn run_app() -> Result<()> { return cmd_update(); } + if args.proxy_env { + let mut port = 1088; + if args.config.exists() { + if let Ok(content) = fs::read_to_string(&args.config) { + let mut stripped = json_comments::StripComments::new(content.as_bytes()); + if let Ok(config) = serde_json::from_reader::<_, UnifiedConfig>(&mut stripped) { + if let AppMode::Client(c) = config.mode { + if let Some(bind) = c.socks5_bind { + if let Some(p) = bind.split(':').last().and_then(|s| s.parse::().ok()) { + port = p; + } + } + } + } + } + } + println!("export http_proxy=\"socks5://127.0.0.1:{}\"", port); + println!("export https_proxy=\"socks5://127.0.0.1:{}\"", port); + println!("export all_proxy=\"socks5://127.0.0.1:{}\"", port); + return Ok(()); + } + + if args.proxy_env_clear { + println!("unset http_proxy"); + println!("unset https_proxy"); + println!("unset all_proxy"); + return Ok(()); + } + if args.generate_key { let mut new_keys = Vec::new(); for _ in 0..args.count { @@ -548,10 +589,77 @@ async fn run_app() -> Result<()> { return Ok(()); } + if let Some(import_url) = args.import { + println!("[ostp] Importing configuration from share link..."); + let client_cfg = parse_ostp_link(&import_url) + .map_err(|e| anyhow!("Share Link Error: {e}"))?; + let unified = UnifiedConfig { + mode: AppMode::Client(client_cfg), + log_level: Some("info".to_string()), + }; + let content = serde_json::to_string_pretty(&unified)?; + if let Some(parent) = args.config.parent() { + if !parent.as_os_str().is_empty() { + fs::create_dir_all(parent)?; + } + } + fs::write(&args.config, content)?; + println!("[ostp] Configuration successfully imported and saved to {:?}", args.config); + return Ok(()); + } + if let Some(url) = args.url { println!("[ostp] Connecting via share link..."); - let client_cfg = parse_ostp_link(&url) + let mut client_cfg = parse_ostp_link(&url) .map_err(|e| anyhow!("Share Link Error: {e}"))?; + + // Interactive prompt for URL launch + use std::io::Write; + + print!("Enable TUN (VPN) mode? [y/N]: "); + std::io::stdout().flush().unwrap(); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + if input.trim().eq_ignore_ascii_case("y") { + if let Some(tun) = &mut client_cfg.tun { + tun.enable = true; + } + } + + print!("Enable connection multiplexing (mux)? [y/N]: "); + std::io::stdout().flush().unwrap(); + input.clear(); + std::io::stdin().read_line(&mut input).unwrap(); + if input.trim().eq_ignore_ascii_case("y") { + print!("How many sessions? [5]: "); + std::io::stdout().flush().unwrap(); + input.clear(); + std::io::stdin().read_line(&mut input).unwrap(); + let mut sessions = 5; + if !input.trim().is_empty() { + if let Ok(s) = input.trim().parse() { + sessions = s; + } + } + if client_cfg.mux.is_none() { + client_cfg.mux = Some(MuxConfig { + enabled: Some(true), + sessions: Some(sessions), + }); + } else if let Some(mux) = &mut client_cfg.mux { + mux.enabled = Some(true); + mux.sessions = Some(sessions); + } + } + + print!("Enable debug mode? [y/N]: "); + std::io::stdout().flush().unwrap(); + input.clear(); + std::io::stdin().read_line(&mut input).unwrap(); + if input.trim().eq_ignore_ascii_case("y") { + client_cfg.debug = Some(true); + } + return run_client_directly(client_cfg).await; }