diff --git a/Cargo.lock b/Cargo.lock index 0e9edbb..8e663d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1317,7 +1317,7 @@ dependencies = [ [[package]] name = "mhrv-rs" -version = "0.7.1" +version = "0.8.0" dependencies = [ "base64 0.22.1", "bytes", @@ -1328,6 +1328,7 @@ dependencies = [ "h2", "http", "httparse", + "libc", "rand", "rcgen", "rustls", diff --git a/Cargo.toml b/Cargo.toml index 2e00b04..1a3bb29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mhrv-rs" -version = "0.7.1" +version = "0.8.0" edition = "2021" description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting" license = "MIT" @@ -46,6 +46,12 @@ flate2 = "1" directories = "5" futures-util = { version = "0.3", default-features = false, features = ["std"] } +# libc is only referenced for the RLIMIT_NOFILE bump on Unix (issue #8 — +# OpenWRT routers ship a very low fd limit that gets hit quickly under normal +# traffic). Already pulled in transitively via tokio, so zero new weight. +[target.'cfg(unix)'.dependencies] +libc = "0.2" + # Optional UI dep: only pulled in when --features ui is set. eframe = { version = "0.28", default-features = false, features = [ "default_fonts", diff --git a/assets/launchers/run.bat b/assets/launchers/run.bat index b15d93a..e8566ce 100644 --- a/assets/launchers/run.bat +++ b/assets/launchers/run.bat @@ -19,12 +19,40 @@ if errorlevel 1 ( echo but HTTPS sites may show certificate warnings until the CA is trusted. ) -if exist "mhrv-rs-ui.exe" ( - echo Starting mhrv-rs UI... - start "" "mhrv-rs-ui.exe" -) else ( +if not exist "mhrv-rs-ui.exe" ( echo UI binary not found. Running CLI proxy instead. mhrv-rs.exe + goto :eof +) + +echo. +echo Starting mhrv-rs UI... +echo (A new window should open. If nothing appears, the UI crashed — the +echo error is shown in this terminal below. Take a screenshot of it and +echo open an issue on github.) +echo. + +REM Run in-place (not via `start`) so if the UI dies on launch, its stderr +REM and non-zero exit code are visible in this window. Previously we used +REM `start "" "mhrv-rs-ui.exe"` which returns immediately and swallows any +REM launch-time crash (issue #7). +mhrv-rs-ui.exe +set UI_EXIT=%ERRORLEVEL% +if not "%UI_EXIT%"=="0" ( + echo. + echo --------------------------------------------------- + echo UI exited with error code %UI_EXIT%. + echo. + echo If this is the first time and you just saw the UI crash immediately, + echo common causes on Windows are: + echo - missing or outdated graphics drivers (try updating) + echo - running inside RDP or a VM without GPU acceleration + echo - antivirus blocking the exe — whitelist the folder and retry + echo. + echo Copy everything above and open an issue on: + echo https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues + echo --------------------------------------------------- + pause ) endlocal diff --git a/assets/openwrt/mhrv-rs.init b/assets/openwrt/mhrv-rs.init index 34924f5..729d990 100644 --- a/assets/openwrt/mhrv-rs.init +++ b/assets/openwrt/mhrv-rs.init @@ -24,6 +24,11 @@ start_service() { procd_set_param stdout 1 procd_set_param stderr 1 procd_set_param file "$CONFIG" + # Issue #8 — OpenWRT's default fd limit is tiny (often 1024 and sometimes + # as low as 256 on constrained devices). A browser's parallel sub-resource + # burst fills it in seconds and accept(2) starts returning EMFILE. 16k is + # plenty for a local proxy and costs virtually no kernel memory. + procd_set_param limits nofile="16384 16384" procd_close_instance } diff --git a/src/bin/ui.rs b/src/bin/ui.rs index 8e13184..d452379 100644 --- a/src/bin/ui.rs +++ b/src/bin/ui.rs @@ -24,6 +24,7 @@ const LOG_MAX: usize = 200; fn main() -> eframe::Result<()> { let _ = rustls::crypto::ring::default_provider().install_default(); + mhrv_rs::rlimit::raise_nofile_limit_best_effort(); let shared = Arc::new(Shared::default()); let (cmd_tx, cmd_rx) = std::sync::mpsc::channel::(); diff --git a/src/lib.rs b/src/lib.rs index 1f1de14..e1d14e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod data_dir; pub mod domain_fronter; pub mod mitm; pub mod proxy_server; +pub mod rlimit; pub mod scan_ips; pub mod scan_sni; pub mod test_cmd; diff --git a/src/main.rs b/src/main.rs index 6c98284..c670189 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,6 +121,10 @@ async fn main() -> ExitCode { // Install default rustls crypto provider (ring). let _ = rustls::crypto::ring::default_provider().install_default(); + // Bump RLIMIT_NOFILE where possible — OpenWRT/Alpine hosts often ship a + // default so low the proxy runs out of fds under normal browser load. + mhrv_rs::rlimit::raise_nofile_limit_best_effort(); + let args = match parse_args() { Ok(a) => a, Err(e) => { diff --git a/src/rlimit.rs b/src/rlimit.rs new file mode 100644 index 0000000..8c860f4 --- /dev/null +++ b/src/rlimit.rs @@ -0,0 +1,66 @@ +//! Best-effort file descriptor limit bump on Unix. +//! +//! Context (issue #8): on OpenWRT routers — and some minimal Alpine / BSD +//! installs — the default `RLIMIT_NOFILE` is so low (often 1024 or even +//! 512) that a browser's burst of ~30 parallel subresource requests fills +//! the limit within seconds. Once the limit is hit `accept(2)` returns +//! `EMFILE` and the user sees: +//! +//! ERROR accept (socks): No file descriptors available (os error 24) +//! +//! This helper raises the soft limit up to the hard limit (without +//! requiring root), so the user gets whatever headroom the kernel +//! already allows them. Failures are logged and swallowed. + +#[cfg(unix)] +pub fn raise_nofile_limit_best_effort() { + // Target: 16384 if the hard limit allows it, else whatever the hard + // limit is. 16k matches what most modern desktop distros default to and + // is plenty for a local proxy. + const DESIRED: u64 = 16_384; + + unsafe { + let mut rl = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + if libc::getrlimit(libc::RLIMIT_NOFILE, &mut rl) != 0 { + let err = std::io::Error::last_os_error(); + tracing::debug!("getrlimit(RLIMIT_NOFILE) failed: {}", err); + return; + } + + // Already high enough? Leave it. + let current = rl.rlim_cur as u64; + let hard = rl.rlim_max as u64; + if current >= DESIRED { + return; + } + + let new_soft = DESIRED.min(hard); + if new_soft <= current { + return; + } + + rl.rlim_cur = new_soft as libc::rlim_t; + if libc::setrlimit(libc::RLIMIT_NOFILE, &rl) != 0 { + let err = std::io::Error::last_os_error(); + tracing::debug!( + "setrlimit(RLIMIT_NOFILE) {} -> {} failed: {}", + current, + new_soft, + err + ); + return; + } + tracing::info!( + "raised RLIMIT_NOFILE: {} -> {} (hard={})", + current, + new_soft, + hard + ); + } +} + +#[cfg(not(unix))] +pub fn raise_nofile_limit_best_effort() {}