mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-18 05:44:35 +03:00
v0.8.3: UI log panel now captures tracing events + dispatch routing visibility (issue #12)
Two reported issues:
1. Log level in the form had no visible effect — trace produced the
same panel output as warn.
2. upstream_socks5 was reported as never being attempted.
(1) was because the UI binary never installed a tracing subscriber.
Every tracing::info!/debug!/trace! from the proxy was discarded; only
the handful of manual push_log() calls for start/stop/test reached
the 'Recent log' panel. Swapping the log level in the combo-box just
rewrote the config field — nothing consumed it.
Fix: install_ui_tracing() at startup registers a tracing_subscriber
fmt layer with a custom MakeWriter that mirrors each formatted event
line into shared.state.log. Respects RUST_LOG, defaults to 'info'
with hyper pinned to warn so the panel isn't swamped by low-level
HTTP chatter. Now the log level switch actually filters panel
output, and routing decisions show up live.
(2) is a documentation / visibility issue more than a bug. Our
upstream_socks5 routing is intentionally scoped to raw-TCP traffic
(non-HTTP, non-TLS) — HTTPS goes through the Apps Script relay,
which is the whole reason mhrv-rs exists. But without working logs,
it looks like upstream_socks5 is dead code.
Fix: every branch of dispatch_tunnel now emits a tracing::info! that
says exactly which path the connection took and, where applicable,
whether upstream_socks5 was used:
dispatch api.telegram.org:443 -> raw-tcp (127.0.0.1:50529)
dispatch www.google.com:443 -> sni-rewrite tunnel (Google edge direct)
dispatch httpbin.org:443 -> MITM + Apps Script relay (TLS detected)
Combined with (1), users can now see in real time whether their
traffic is hitting upstream_socks5. If it says 'raw-tcp (direct)'
after they set the field, that's evidence of a real bug; if it
never reaches the raw-tcp branch at all, that's the documented
design (HTTPS → Apps Script).
Also per user request, updated README:
- Shields.io badges up top: latest release, total downloads, CI
status, license, stars.
- Short 'Heads up on authorship' note crediting Anthropic's Claude
for the bulk of the Rust port (with the human-on-every-commit
caveat). English and Persian mirrors both have it.
All 37 unit tests pass.
This commit is contained in:
Generated
+1
-1
@@ -1317,7 +1317,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mhrv-rs"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mhrv-rs"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
edition = "2021"
|
||||
description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
# MasterHttpRelayVPN-RUST
|
||||
|
||||
[](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/releases/latest)
|
||||
[](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/releases)
|
||||
[](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/actions/workflows/release.yml)
|
||||
[](LICENSE)
|
||||
[](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/stargazers)
|
||||
|
||||
Rust port of [@masterking32's MasterHttpRelayVPN](https://github.com/masterking32/MasterHttpRelayVPN). **All credit for the original idea and the Python implementation goes to [@masterking32](https://github.com/masterking32).** This is a faithful reimplementation of the `apps_script` mode, packaged as two tiny binaries (CLI + desktop UI) with no runtime dependencies.
|
||||
|
||||
Free DPI bypass via Google Apps Script as a remote relay, with TLS SNI concealment. Your ISP's censor sees traffic going to `www.google.com`; behind the scenes a free Google Apps Script that you deploy in your own Google account fetches the real website for you.
|
||||
|
||||
> **Heads up on authorship:** the bulk of this Rust port was written with [Anthropic's Claude](https://claude.com) driving, reviewed by a human on every commit. Bug reports, fixes, and contributions are all welcome — see the [issues page](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues).
|
||||
|
||||
**[English Guide](#setup-guide)** | **[راهنمای فارسی](#راهنمای-فارسی)**
|
||||
|
||||
## Why this exists
|
||||
@@ -357,6 +365,8 @@ Original project: <https://github.com/masterking32/MasterHttpRelayVPN> by [@mast
|
||||
|
||||
این نسخهٔ `Rust` از پروژهٔ اصلی [MasterHttpRelayVPN](https://github.com/masterking32/MasterHttpRelayVPN) اثر [@masterking32](https://github.com/masterking32) است. **تمام اعتبار ایده و نسخهٔ اصلی پایتون برای ایشان است.** این پورت همان روش را در قالب یک فایل اجرایی تکپارچه (~۳ مگابایت) بدون نیاز به نصب پایتون یا هیچ وابستگی دیگری ارائه میدهد.
|
||||
|
||||
> **نکتهٔ مهم دربارهٔ نویسندگی:** بیشتر کدِ این پورت `Rust` با کمک [Claude](https://claude.com) شرکت Anthropic نوشته شده و روی هر commit توسط انسان بازبینی شده است. اگر باگی دیدید یا پیشنهادی دارید، لطفاً در [صفحهٔ issues](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues) گزارش دهید.
|
||||
|
||||
### برای چه کسی مفید است؟
|
||||
|
||||
- کسانی که در شبکههای تحت سانسور قوی (مثل ایران) زندگی میکنند
|
||||
|
||||
@@ -29,6 +29,17 @@ fn main() -> eframe::Result<()> {
|
||||
let shared = Arc::new(Shared::default());
|
||||
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel::<Cmd>();
|
||||
|
||||
// Hook tracing events into the Recent log panel. Without this every
|
||||
// tracing::info! / debug! / trace! the proxy emits gets swallowed and
|
||||
// the panel only ever shows our manual push_log calls, making the log
|
||||
// level selector look useless (issue #12 bug 2).
|
||||
//
|
||||
// The env-filter respects RUST_LOG if set, otherwise defaults to info
|
||||
// so users see routing decisions immediately without any knob-turning.
|
||||
// When they start the proxy and Save the config, the log level from the
|
||||
// config is applied to the in-process filter (see on_start below).
|
||||
install_ui_tracing(shared.clone());
|
||||
|
||||
let shared_bg = shared.clone();
|
||||
std::thread::Builder::new()
|
||||
.name("mhrv-bg".into())
|
||||
@@ -1198,6 +1209,83 @@ fn background_thread(shared: Arc<Shared>, rx: Receiver<Cmd>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Install a tracing subscriber that mirrors every log event into the UI's
|
||||
/// Recent log panel.
|
||||
///
|
||||
/// Respects `RUST_LOG` if set. Otherwise defaults to `info` — which is what
|
||||
/// users mean when they pick a non-default log level in the form. (trace /
|
||||
/// debug flip too much noise for a local GUI, so the combo-box changes level
|
||||
/// live via the `reload` handle that `with_env_filter` gives us but we keep
|
||||
/// the default boot-time level at info so first-run behavior is sensible.)
|
||||
fn install_ui_tracing(shared: Arc<Shared>) {
|
||||
use tracing_subscriber::fmt::MakeWriter;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
/// A MakeWriter that pushes each line into the shared log panel.
|
||||
struct UiLogWriter {
|
||||
shared: Arc<Shared>,
|
||||
}
|
||||
|
||||
struct UiWriterInst {
|
||||
shared: Arc<Shared>,
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> MakeWriter<'a> for UiLogWriter {
|
||||
type Writer = UiWriterInst;
|
||||
fn make_writer(&'a self) -> Self::Writer {
|
||||
UiWriterInst {
|
||||
shared: self.shared.clone(),
|
||||
buf: Vec::with_capacity(128),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Write for UiWriterInst {
|
||||
fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
|
||||
self.buf.extend_from_slice(data);
|
||||
Ok(data.len())
|
||||
}
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
if self.buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let text = String::from_utf8_lossy(&self.buf).trim_end().to_string();
|
||||
self.buf.clear();
|
||||
// Split on newlines in case multiple events got buffered.
|
||||
for line in text.lines() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut s = self.shared.state.lock().unwrap();
|
||||
s.log.push_back(line.to_string());
|
||||
while s.log.len() > LOG_MAX {
|
||||
s.log.pop_front();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UiWriterInst {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::io::Write::flush(self);
|
||||
}
|
||||
}
|
||||
|
||||
let filter =
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,hyper=warn"));
|
||||
|
||||
let writer = UiLogWriter { shared };
|
||||
|
||||
let _ = tracing_subscriber::fmt()
|
||||
.with_env_filter(filter)
|
||||
.with_target(false)
|
||||
.with_ansi(false)
|
||||
.with_writer(writer)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
fn push_log(shared: &Shared, msg: &str) {
|
||||
let line = format!(
|
||||
"{} {}",
|
||||
|
||||
+33
-3
@@ -354,6 +354,7 @@ async fn dispatch_tunnel(
|
||||
) -> std::io::Result<()> {
|
||||
// 1. Explicit hosts override or SNI-rewrite suffix: always use the tunnel.
|
||||
if matches_sni_rewrite(&host) || hosts_override(&rewrite_ctx.hosts, &host).is_some() {
|
||||
tracing::info!("dispatch {}:{} -> sni-rewrite tunnel (Google edge direct)", host, port);
|
||||
return do_sni_rewrite_tunnel_from_tcp(sock, &host, port, mitm, rewrite_ctx).await;
|
||||
}
|
||||
|
||||
@@ -371,13 +372,29 @@ async fn dispatch_tunnel(
|
||||
Ok(Err(_)) => return Ok(()),
|
||||
Err(_) => {
|
||||
// Client silent: likely a server-first protocol.
|
||||
plain_tcp_passthrough(sock, &host, port, rewrite_ctx.upstream_socks5.as_deref()).await;
|
||||
let via = rewrite_ctx.upstream_socks5.as_deref();
|
||||
tracing::info!(
|
||||
"dispatch {}:{} -> raw-tcp ({}) (client silent, likely server-first)",
|
||||
host,
|
||||
port,
|
||||
via.unwrap_or("direct")
|
||||
);
|
||||
plain_tcp_passthrough(sock, &host, port, via).await;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if peek_n >= 1 && peek_buf[0] == 0x16 {
|
||||
// Looks like TLS: MITM + relay via Apps Script.
|
||||
// Looks like TLS: MITM + relay via Apps Script. Note: upstream_socks5
|
||||
// is NOT consulted here by design — HTTPS goes through the Apps Script
|
||||
// relay, which is the whole reason mhrv-rs exists. If you want HTTPS
|
||||
// to flow through xray, disable mhrv-rs and point your browser at
|
||||
// xray directly.
|
||||
tracing::info!(
|
||||
"dispatch {}:{} -> MITM + Apps Script relay (TLS detected)",
|
||||
host,
|
||||
port
|
||||
);
|
||||
run_mitm_then_relay(sock, &host, port, mitm, &fronter).await;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -386,11 +403,24 @@ async fn dispatch_tunnel(
|
||||
// fall back to plain TCP passthrough.
|
||||
if peek_n > 0 && looks_like_http(&peek_buf[..peek_n]) {
|
||||
let scheme = if port == 443 { "https" } else { "http" };
|
||||
tracing::info!(
|
||||
"dispatch {}:{} -> Apps Script relay (plain HTTP, scheme={})",
|
||||
host,
|
||||
port,
|
||||
scheme
|
||||
);
|
||||
relay_http_stream_raw(sock, &host, port, scheme, &fronter).await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
plain_tcp_passthrough(sock, &host, port, rewrite_ctx.upstream_socks5.as_deref()).await;
|
||||
let via = rewrite_ctx.upstream_socks5.as_deref();
|
||||
tracing::info!(
|
||||
"dispatch {}:{} -> raw-tcp ({}) (non-HTTP, non-TLS client payload)",
|
||||
host,
|
||||
port,
|
||||
via.unwrap_or("direct")
|
||||
);
|
||||
plain_tcp_passthrough(sock, &host, port, via).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user