Skip to content

Commit d817028

Browse files
committed
feat: v1.8.2 — UI tracing reads config.log_level + softer decoy detection
- src/bin/ui.rs: install_ui_tracing now takes config_level. Filter precedence is RUST_LOG > config.log_level > info,hyper=warn. The filter is wrapped in a reload::Layer; Save reinstalls it via apply_log_level so users don't need to restart for a level change. Fixes #401 (w0l4i) — config.log_level was previously dead on the UI binary even though the CLI honored it via init_logging. - src/tunnel_client.rs: v1.8.1 asserted "AUTH_KEY mismatch" on the Apps Script placeholder body, but #404 (w0l4i) showed mixed success/failure on the same script_id, which rules that out. The body is also returned for Apps Script execution timeout, quota tear, internal hiccup, and ISP-side truncation. Error message now enumerates all four candidates and points to DIAGNOSTIC_MODE for disambiguation.
1 parent fd865df commit d817028

5 files changed

Lines changed: 129 additions & 39 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mhrv-rs"
3-
version = "1.8.1"
3+
version = "1.8.2"
44
edition = "2021"
55
description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting"
66
license = "MIT"

docs/changelog/v1.8.2.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!-- see docs/changelog/v1.1.0.md for the file format: Persian, then `---`, then English. -->
2+
• اصلاح log level در UI binary (Windows + Android) ([#401](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/401)): قبلاً `mhrv-rs-ui` (و Android) فیلتر tracing رو فقط از `RUST_LOG` env var یا default `info,hyper=warn` می‌خوند — مقدار `log_level` در `config.json` در عمل ignore می‌شد. فرم UI combobox `log_level` داشت ولی هیچ‌جا به subscriber اعمال نمی‌شد. حالا precedence اینه: `RUST_LOG` (اگر set باشد) > `config.log_level` > `info,hyper=warn`. علاوه بر این Save در UI الان log level رو live اعمال می‌کنه (بدون نیاز به restart) از طریق reload handle. CLI `mhrv-rs` از قبل درست کار می‌کرد — این فقط fix UI bin بود.
3+
• پیغام تشخیص decoy ملایم‌تر — به‌جای assert AUTH_KEY mismatch، چهار علت ممکن enumerate می‌کنه ([#404](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/404)): @w0l4i گزارش داد همان `script_id` گاهی decoy و گاهی موفقیت برمی‌گرده در یک دقیقه — یعنی NOT AUTH_KEY mismatch (اگر بود ۱۰۰٪ fail می‌گرفت). تحقیق نشون داد body string `"The script completed but did not return anything"` اختصاصی به decoy v1.8.0 ما نیست — Apps Script همان body رو در ۴ سناریو برمی‌گردونه: (۱) AUTH_KEY mismatch (decoy ما، intentional)، (۲) Apps Script execution timeout یا quota tear، (۳) Google-side internal hiccup، (۴) ISP-side response truncation (#313 pattern). Error message v1.8.1 false positive داشت در سناریو ۲-۴. حالا پیغام: "got the v1.8.0 decoy/placeholder body — could be (1) AUTH_KEY mismatch, (2) Apps Script execution timeout/quota tear, (3) Apps Script internal hiccup, (4) ISP-side response truncation. Set DIAGNOSTIC_MODE=true to disambiguate (1) — only AUTH_KEY mismatch returns this body in diagnostic mode." کاربر action درست رو کشف می‌کنه.
4+
---
5+
• Fix log level on the UI binary (Windows + Android) ([#401](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/401)): previously `mhrv-rs-ui` (and Android, which uses the same JNI path) installed its tracing filter from `RUST_LOG` only — falling back to `info,hyper=warn` when unset. The `log_level` field in `config.json` was effectively ignored, even though the UI form has a combobox that writes to it. The CLI binary (`mhrv-rs`) read `config.log_level` correctly via `init_logging()`; only the UI binary was broken. New precedence: `RUST_LOG` (explicit override) > `config.log_level` (what the user picked in the form) > `info,hyper=warn` (default). The Save button now also reinstalls the filter live via a `tracing_subscriber::reload::Handle`, so users don't need to restart for a level change to take effect. RUST_LOG still wins if set at boot — explicit override beats config in both directions.
6+
• Soften the v1.8.1 decoy detection error message — enumerate four candidate causes instead of asserting AUTH_KEY mismatch ([#404](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/404)): @w0l4i reported the same `script_id` mixing decoy ERROR with successful batches inside a one-minute window — which rules out AUTH_KEY mismatch as the cause (a real mismatch fails 100% of batches against that deployment, never succeeds intermittently). Investigation showed the body string `"The script completed but did not return anything"` is **not** unique to our v1.8.0 bad-auth path — Apps Script itself returns the same body in three other unrelated cases: (2) Apps Script execution timeout or per-100s quota tear, (3) Google-side internal runtime hiccup, (4) ISP-side response truncation mid-flight (the #313 pattern). The v1.8.1 error message was a false positive in scenarios 2-4. The v1.8.2 message now reads: "got the v1.8.0 decoy/placeholder body — could be (1) AUTH_KEY mismatch (run a direct curl probe against the deployment to verify), (2) Apps Script execution timeout or per-100s quota tear (try lowering parallel_concurrency), (3) Apps Script internal hiccup (transient, retry next batch), or (4) ISP-side response truncation (#313 pattern, try a different google_ip). To distinguish (1) from the rest: set DIAGNOSTIC_MODE=true at the top of Code.gs + redeploy as new version — only AUTH_KEY mismatch returns this body in diagnostic mode." Users now have an actionable narrowing procedure instead of a confidently-wrong assertion.

src/bin/ui.rs

Lines changed: 93 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,36 @@ fn main() -> eframe::Result<()> {
3333
let shared = Arc::new(Shared::default());
3434
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel::<Cmd>();
3535

36+
// Load the user's saved form first so we can seed the tracing filter
37+
// with their saved log level. Otherwise the form's log-level combobox
38+
// would only ever take effect via env var or after Save → restart, and
39+
// users on the UI binary (issue #401) reasonably expect the saved
40+
// config.json `log_level` to apply at boot like it does for the CLI.
41+
let (form, load_err) = load_form();
42+
let initial_toast = load_err.map(|e| (e, Instant::now()));
43+
3644
// Hook tracing events into the Recent log panel. Without this every
3745
// tracing::info! / debug! / trace! the proxy emits gets swallowed and
3846
// the panel only ever shows our manual push_log calls, making the log
3947
// level selector look useless (issue #12 bug 2).
4048
//
41-
// The env-filter respects RUST_LOG if set, otherwise defaults to info
42-
// so users see routing decisions immediately without any knob-turning.
43-
// When they start the proxy and Save the config, the log level from the
44-
// config is applied to the in-process filter (see on_start below).
45-
install_ui_tracing(shared.clone());
49+
// Filter precedence (issue #401 fix in v1.8.2):
50+
// 1. RUST_LOG env var if set — explicit override
51+
// 2. Saved config's `log_level` (passed from form) — what users mean
52+
// when they pick a level in the UI
53+
// 3. "info,hyper=warn" — sensible default
54+
//
55+
// Save inside the running UI also installs the new filter via the
56+
// reload handle (see `LOG_RELOAD` below), so users don't need to
57+
// restart for a config change to take effect.
58+
install_ui_tracing(shared.clone(), &form.log_level);
4659

4760
let shared_bg = shared.clone();
4861
std::thread::Builder::new()
4962
.name("mhrv-bg".into())
5063
.spawn(move || background_thread(shared_bg, cmd_rx))
5164
.expect("failed to spawn background thread");
5265

53-
let (form, load_err) = load_form();
54-
let initial_toast = load_err.map(|e| (e, Instant::now()));
55-
5666
// Pick the renderer. Default is `glow` (OpenGL 2+) because that's
5767
// what we shipped through v1.0.x and it has the least binary-size
5868
// overhead. Users on older Windows boxes / RDP sessions / headless
@@ -993,7 +1003,12 @@ impl eframe::App for App {
9931003
ui.horizontal(|ui| {
9941004
if ui.add(primary_button("Save config")).clicked() {
9951005
match self.form.to_config().and_then(|c| save_config(&c)) {
996-
Ok(p) => self.toast = Some((format!("Saved to {}", p.display()), Instant::now())),
1006+
Ok(p) => {
1007+
// Apply the new log level live so users don't have to
1008+
// restart for the combobox to take effect (#401).
1009+
apply_log_level(&self.form.log_level);
1010+
self.toast = Some((format!("Saved to {}", p.display()), Instant::now()));
1011+
}
9971012
Err(e) => self.toast = Some((format!("Save failed: {}", e), Instant::now())),
9981013
}
9991014
}
@@ -2193,14 +2208,19 @@ fn background_thread(shared: Arc<Shared>, rx: Receiver<Cmd>) {
21932208
/// Install a tracing subscriber that mirrors every log event into the UI's
21942209
/// Recent log panel.
21952210
///
2196-
/// Respects `RUST_LOG` if set. Otherwise defaults to `info` — which is what
2197-
/// users mean when they pick a non-default log level in the form. (trace /
2198-
/// debug flip too much noise for a local GUI, so the combo-box changes level
2199-
/// live via the `reload` handle that `with_env_filter` gives us but we keep
2200-
/// the default boot-time level at info so first-run behavior is sensible.)
2201-
fn install_ui_tracing(shared: Arc<Shared>) {
2211+
/// Filter precedence (issue #401, v1.8.2):
2212+
/// 1. `RUST_LOG` env var, if set
2213+
/// 2. The saved form's `log_level` (passed in from the loaded config)
2214+
/// 3. `info,hyper=warn` as a sensible default
2215+
///
2216+
/// The constructed filter is wrapped in a `reload::Layer` and the handle
2217+
/// is stashed in `LOG_RELOAD` so that a Save inside the running UI can
2218+
/// reinstall the filter without a restart. See `apply_log_level`.
2219+
fn install_ui_tracing(shared: Arc<Shared>, config_level: &str) {
22022220
use tracing_subscriber::fmt::MakeWriter;
2203-
use tracing_subscriber::EnvFilter;
2221+
use tracing_subscriber::layer::SubscriberExt;
2222+
use tracing_subscriber::util::SubscriberInitExt;
2223+
use tracing_subscriber::{reload, EnvFilter};
22042224

22052225
/// A MakeWriter that pushes each line into the shared log panel.
22062226
struct UiLogWriter {
@@ -2254,19 +2274,71 @@ fn install_ui_tracing(shared: Arc<Shared>) {
22542274
}
22552275
}
22562276

2257-
let filter =
2258-
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,hyper=warn"));
2277+
// RUST_LOG > config.log_level > "info,hyper=warn"
2278+
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
2279+
let trimmed = config_level.trim();
2280+
if trimmed.is_empty() {
2281+
EnvFilter::new("info,hyper=warn")
2282+
} else {
2283+
EnvFilter::try_new(trimmed).unwrap_or_else(|_| EnvFilter::new("info,hyper=warn"))
2284+
}
2285+
});
2286+
2287+
let (filter_layer, reload_handle) = reload::Layer::new(filter);
2288+
if LOG_RELOAD.set(reload_handle).is_err() {
2289+
// Already initialized — install_ui_tracing got called twice. Bail
2290+
// silently rather than panic; the existing subscriber stays live.
2291+
return;
2292+
}
22592293

22602294
let writer = UiLogWriter { shared };
22612295

2262-
let _ = tracing_subscriber::fmt()
2263-
.with_env_filter(filter)
2296+
let fmt_layer = tracing_subscriber::fmt::layer()
22642297
.with_target(false)
22652298
.with_ansi(false)
2266-
.with_writer(writer)
2299+
.with_writer(writer);
2300+
2301+
let _ = tracing_subscriber::registry()
2302+
.with(filter_layer)
2303+
.with(fmt_layer)
22672304
.try_init();
22682305
}
22692306

2307+
/// Reload handle for the UI's tracing EnvFilter — populated once at startup
2308+
/// by `install_ui_tracing`. `apply_log_level` uses it to swap in a new
2309+
/// filter when the user clicks Save with a different log level (#401).
2310+
static LOG_RELOAD: std::sync::OnceLock<
2311+
tracing_subscriber::reload::Handle<
2312+
tracing_subscriber::EnvFilter,
2313+
tracing_subscriber::Registry,
2314+
>,
2315+
> = std::sync::OnceLock::new();
2316+
2317+
/// Reinstall the tracing filter at runtime. Called from the Save handler
2318+
/// so the user's new `log_level` takes effect without a restart. RUST_LOG
2319+
/// still wins if it was set at process start — explicit override beats
2320+
/// config in both directions.
2321+
fn apply_log_level(level: &str) {
2322+
use tracing_subscriber::EnvFilter;
2323+
let Some(handle) = LOG_RELOAD.get() else {
2324+
return;
2325+
};
2326+
if std::env::var_os("RUST_LOG").is_some() {
2327+
// RUST_LOG was set explicitly at boot — don't silently override.
2328+
return;
2329+
}
2330+
let trimmed = level.trim();
2331+
let new = if trimmed.is_empty() {
2332+
EnvFilter::new("info,hyper=warn")
2333+
} else {
2334+
match EnvFilter::try_new(trimmed) {
2335+
Ok(f) => f,
2336+
Err(_) => return,
2337+
}
2338+
};
2339+
let _ = handle.modify(|f| *f = new);
2340+
}
2341+
22702342
/// Where we drop downloaded release assets. Prefer the OS user Downloads
22712343
/// dir (via the directories crate that's already in our tree), fall back
22722344
/// to the user-data dir for platforms that don't expose one (edge case).

src/tunnel_client.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -881,24 +881,36 @@ async fn fire_batch(
881881
}
882882
let err_msg = format!("{}", e);
883883
let sid_short = &script_id[..script_id.len().min(8)];
884-
// Detect the v1.8.0 bad-auth decoy HTML body. The relay layer
885-
// wraps any non-JSON response in `BadResponse("no json in
886-
// batch response: <body prefix>")`. The decoy body string
887-
// `"The script completed but did not return anything"` is
888-
// distinctive — Apps Script's stock pages never include it,
889-
// and our own `Code.gs` only returns it when AUTH_KEY check
890-
// fails. Surfacing this as an actionable hint saves users
891-
// (and #404 / #310 sina-b4hrm class issues) hours of
892-
// staring at "no json in batch response".
884+
// Detect the body string we ship as the v1.8.0 bad-auth
885+
// decoy. v1.8.1 asserted "AUTH_KEY mismatch" outright, but
886+
// #404 (w0l4i) found the same body comes back from Apps
887+
// Script in 3 other unrelated cases too:
888+
//
889+
// 1. AUTH_KEY mismatch — our intentional decoy
890+
// 2. Apps Script execution timeout/ — runtime hit 6-min
891+
// mid-call quota tear cap or per-100s quota
892+
// 3. Apps Script internal hiccup — Google-side flake,
893+
// serves placeholder
894+
// 4. ISP-side response truncation — #313 pattern, the
895+
// response was assembled
896+
// but ate an RST mid-flight
897+
//
898+
// So we surface all four candidates instead of asserting #1.
899+
// Users can flip DIAGNOSTIC_MODE=true in Code.gs to disambiguate:
900+
// only #1 still returns the decoy in diagnostic mode; the
901+
// others return real JSON or different errors.
893902
if err_msg.contains("The script completed but did not return anything") {
894903
tracing::error!(
895-
"batch failed (script {}): got the v1.8.0 bad-auth decoy — \
896-
your AUTH_KEY in mhrv-rs config does NOT match the AUTH_KEY \
897-
in this deployment's Code.gs. Either fix the mismatch + \
898-
redeploy as a NEW VERSION (Apps Script doesn't auto-pick-up \
899-
AUTH_KEY edits without an explicit redeploy), or set \
900-
DIAGNOSTIC_MODE=true at the top of Code.gs + redeploy to \
901-
see the explicit JSON `unauthorized` error during setup.",
904+
"batch failed (script {}): got the v1.8.0 decoy/placeholder body — \
905+
could be (1) AUTH_KEY mismatch between mhrv-rs config and Code.gs \
906+
(run a direct curl probe against the deployment to verify), \
907+
(2) Apps Script execution timeout or per-100s quota tear (try \
908+
lowering parallel_concurrency in config), (3) Apps Script \
909+
internal hiccup (transient, retry next batch), or (4) ISP-side \
910+
response truncation (#313 pattern, try a different google_ip). \
911+
To distinguish (1) from the rest: set DIAGNOSTIC_MODE=true at \
912+
the top of Code.gs + redeploy as new version — only AUTH_KEY \
913+
mismatch returns this body in diagnostic mode.",
902914
sid_short
903915
);
904916
} else {

0 commit comments

Comments
 (0)