Commit Graph

39 Commits

Author SHA1 Message Date
dazzling-no-more 1d14930887 feat(cert): add --remove-cert flag and Remove CA button for clean-slate revocation (#121)
* feat(cert): add --remove-cert flag and Remove CA button for clean-slate revocation

* fix(cert): testable euid-root branch + orphan enterprise_roots warning
2026-04-26 19:20:08 +03:00
dazzling-no-more 40c2b6c509 feat(udp): SOCKS5 UDP ASSOCIATE relay through full tunnel
Adds end-to-end UDP support: SOCKS5 client UDP ASSOCIATE → tunnel-mux
udp_open/udp_data ops → tunnel-node UDP sessions → real UDP to upstream.
QUIC/HTTP3, DNS, and STUN now traverse full mode without falling back to
TCP or leaking outside the tunnel.

Apps Script proxies the new ops opaquely through the existing batch
endpoint; CodeFull.gs only gets a doc-comment update.

Highlights:
- proxy_server.rs: SOCKS5 UDP ASSOCIATE handler with per-session task,
  bounded uplink mpsc channel, adaptive empty-poll backoff (500 ms → 30 s),
  source-IP validation against the control TCP peer, port-locking on
  first valid datagram, and self-removal from the dispatch map on eof.
- tunnel_client.rs: UdpOpen / UdpData / close_session mux variants
  alongside the existing TCP plumbing; pkts decoder helper.
- tunnel-node: UdpSessionInner with bounded VecDeque queue, drop-oldest
  on overflow with queue_drops counter and warn-then-throttled logs,
  last_active refreshed only on real activity (uplink send or upstream
  recv — empty polls do not refresh), independent TCP/UDP drain in
  handle_batch Phase 2, separate active-drain (150 ms) and retry
  (250 ms) windows for UDP, idle long-poll (5 s).
- Tests: SOCKS5 UDP packet parser (IPv4/IPv6/DOMAIN round-trips,
  truncation rejects, fragmented rejects), UDP queue overflow drop +
  counter, regression test that batch with both UDP and TCP-data ops
  still runs the TCP retry pass.

Docs: README + android.{md,fa.md} updated to reflect UDP availability
in full mode; tunnel-node/README documents the new ops.
2026-04-25 16:19:23 +04:00
therealaleph fb552c227d v1.5.0: long-poll Full Tunnel + Docker tunnel-node + brief FA release notes
Ships PR #173 (event-driven drain) plus three operational improvements:

PR #173 — long-poll tunnel mode. The tunnel-node's batch drain
switched from a fixed 150 ms sleep to an event-driven Notify wait;
idle sessions long-poll up to 5 s and wake on the first byte from
upstream. Push notifications and chat messages now arrive in roughly
RTT instead of waiting for the next client poll tick. Backward compat
with pre-#173 tunnel-nodes is automatic via a sticky AtomicBool that
detects fast empty replies and reverts to the legacy cadence.
92 client tests + 17 tunnel-node tests pass, including end-to-end
TCP-pair verification of the notify wiring.

Docker image for tunnel-node. Adds a hardened Dockerfile (BuildKit
cache mounts, non-root runtime user, ca-certificates for HTTPS
upstreams) and a .dockerignore to keep build context small. New
`tunnel-docker` job in the release workflow builds + pushes
multi-arch (linux/amd64 + linux/arm64) to
ghcr.io/therealaleph/mhrv-tunnel-node with `:latest`, `:1.5`, and
`:1.5.0` tags on every release. Setting up Full Tunnel mode goes
from "rustup + cargo build on a 1 GB VPS" (which fails on memory
half the time) to a one-liner. tunnel-node/README.md updated with
prebuilt-image + docker-compose recipes.

Brief Persian release note in Telegram caption. The release-post
caption now leads with a `<blockquote>`-wrapped FA bullet headlines
extracted from `docs/changelog/v<ver>.md`, above the existing two
links (repo + release). Markdown links → Telegram HTML <a> for
clickability. Cap-budget-aware truncation at bullet boundaries
keeps total caption under Telegram's 1024-char limit. Headlines-only
rather than full bullets so multiple "what's new" items fit
comfortably (the full bullets remain on the GH release page and as
the optional --with-changelog reply-threaded message).

GitHub Releases page bodies now lead with the changelog content
(Persian section + `---` + English) instead of just a Full Changelog
comparison link. The auto comparison link is appended at the bottom
via `append_body: true` rather than removed.

Workflow changes:
- New `permissions: packages: write` at the workflow level (required
  for ghcr push via docker/login-action).
- New `tunnel-docker` job needs `build` (not the full matrix) to
  serialize the QEMU buildx layer with the matrix cache.
- Release job composes the body from `docs/changelog/v${VER}.md`
  in a pre-step that handles both tag-push and workflow_dispatch
  paths (uses inputs.version || github.ref_name like the rest of
  the workflow).

Tested locally:
- `cargo test` — 92 lib tests pass
- `cargo test -p mhrv-tunnel-node` — 17 tests pass
- `docker build` of tunnel-node Dockerfile — 32 MB image, runs as
  non-root, /health returns "ok", auth rejection works correctly,
  legitimate requests open sessions to remote hosts
- Telegram script `--dry-run` mode added; rendered captions for
  v1.4.0, v1.4.1, v1.5.0 all fit under 900 chars

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 11:56:41 +03:00
therealaleph d73639a42c docs: acknowledge IP-exposure caveat for apps_script mode (fix #148)
@creep247 raised a fair concern: v1.2.9's forwarded-header stripping
handles the client-side leg (browser extensions / local proxies
inserting X-Forwarded-For before the request reaches Apps Script),
but it cannot cover whatever Google's infrastructure may add when
the Apps Script runtime's subsequent UrlFetchApp.fetch() hits the
target server — that leg is outside this client's control.

Added a paragraph to both the English and Persian "Security posture"
sections making the model honest:
  - what v1.2.9's stripping DOES cover (client-side added headers)
  - what it DOES NOT cover (Google's internal header chain on the
    fetch from Apps Script runtime → destination)
  - recommendation: users whose threat model requires the destination
    site cannot under any circumstances learn their IP should use
    Full Tunnel mode, which exits via the user's own VPS end-to-end

No code change — the privacy claim is narrower than a naive reading
of "v1.2.9 fixed the IP leak" might suggest, so the docs should say
so explicitly rather than let users over-trust the apps_script mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:59:04 +03:00
vahidlazio 2715991afa feat: per-deployment concurrency (30 req/account) instead of global pipeline depth
Apps Script enforces 30 concurrent executions per account. The old pipeline
used a single global semaphore sized to the number of deployments, meaning
1 in-flight batch per deployment. Now each deployment ID gets its own
semaphore with 30 permits — matching the actual per-account limit.

With N deployments the system can sustain 30×N concurrent batch requests
instead of N.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 21:10:06 +02:00
Shin (Former Aleph) 81d1883f45 Update README.md 2026-04-24 21:20:34 +03:00
therealaleph 8466a2622b docs: add SF_README.md (short, beginner-friendly guide) — fix #135
Adds a Straightforward README that's short, plain-language, and skips
the technical "how it works" diagrams. Covers the same setup flow as
the main README but with friendlier wording, plus a "Common issues"
section that surfaces the most-asked-about gotchas (YouTube SafeSearch
loop #61, Cloudflare Turnstile loop, 504 Relay timeout, daily quota,
"connected but nothing loads"). Both English and Persian.

The main README's index line now offers four links instead of two:
  - Quick Start (EN)
  - Full English README
  - راهنمای خلاصه فارسی
  - راهنمای کامل فارسی

So users who don't have a deep networking background can land on a
guide tailored to them, and users who want the full picture still
have a single click.

Closes #135.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:50:30 +03:00
Shin (Former Aleph) 40c9e2f9ba Merge pull request #131 from vahidlazio/feat/uncap-pipeline-depth
feat: remove pipeline depth cap — scales with deployment count
2026-04-24 20:30:16 +03:00
vahidlazio d091c48da6 feat: remove pipeline depth cap — scales with deployment count
Pipeline depth was artificially capped at 12. Users with 20+
deployments across multiple accounts were wasting pipeline capacity.

Now: pipeline_depth = num_scripts (minimum 2, no upper cap).
The connection pool (80) is the natural ceiling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 19:00:21 +02:00
therealaleph 1db6845c15 readme: add Support button + sections (EN + FA)
Adds:
- Shield badge in the top badge row linking to https://sh1n.org/donate
- Dedicated "Support this project" section in English (above the
  ltr/rtl divider) explaining what donations cover
- Dedicated "حمایت از پروژه" section in Persian mirroring the same
  content inside the RTL block

Donations cover hosting, self-hosted CI runner costs, and continued
maintenance. Starring the repo remains the free equivalent.
2026-04-24 18:08:16 +03:00
therealaleph 8dea9d38a4 docs: mirror the Persian FAQ clarification for deployment IDs (#126 follow-up)
My earlier commit fe84db0 fixed the English quick-start but missed
that vahidlazio's PR #126 also rewrote the Persian FAQ entry
\"چند Deployment ID لازم دارم؟\" with the same correction. Mirroring
that here so both language tracks say the same thing.

Persian wording credit to @vahidlazio from #126.
2026-04-24 17:35:50 +03:00
therealaleph fe84db0fe0 docs: clarify full-tunnel deployment IDs — same account is fine (#126)
Vahidlazio flagged that the README's full-mode quick-start read as
"you need 3-12 separate Google accounts," which is wrong — you need
3-12 deployment IDs, which can all live on one Google account (each
"New deployment" produces its own ID). Going multi-account only
buys daily-quota headroom; the pipeline depth itself scales fine on
one account up to Apps Script's simultaneous-execution ceiling.

Also rewrites the accompanying recipe into three concrete tiers
(solo / small group / large group) instead of waving vaguely at a
range.

Closes #126. Also obsoletes my earlier analysis on #61 where I told
@Feiabyte that same-account multi-deployment "does not give
throughput" — that was wrong; it does, because simultaneous Apps
Script executions aren't bottlenecked by a per-deployment limit
until you pile up more than ~30 concurrent executions per account,
which 12 deployments don't come close to. Posting a correction on
#61 separately.
2026-04-24 17:34:37 +03:00
vahidlazio b73bbe2106 feat: Mode::Full + batch tunnel client (#94)
Adds a new `mode: full` that tunnels ALL traffic end-to-end through Apps Script → a remote tunnel node. Browser does TLS directly with the destination. No MITM, no CA installation needed on the client device.

Ships as part of the 3-PR series: #93 (tunnel-node service + CodeFull.gs, merged) + this (Rust-side Mode::Full + batch tunnel client) + #95 (Android UI dropdown, now rolled into this PR post-rebase).

### Architecture
- Client → mhrv-rs → script.google.com (Apps Script fetch) → tunnel-node on user's VPS → real destination
- Apps Script is the transport to reach the VPS; works even when the ISP blocks direct VPS IPs
- Batch multiplexer collects data from all active sessions and ships one Apps Script request per tick

### Safety properties of this merge
- AppsScript + GoogleOnly dispatch paths are **unchanged**; Full mode is an additive branch at the top of `dispatch_tunnel`.
- `tunnel_client.rs` is a new isolated module (387 LOC).
- `tunnel_request()` is a new method on `DomainFronter`, no change to `relay()` / `relay_parallel_range()`.
- Config: additive `Mode::Full` variant + validation tests (2 new); existing validation rules untouched.
- Local build: clean compile. `cargo test --quiet`: 75 passed (73 → 75 with 2 new config tests).

### Closes
Unblocks the feature requested in #61, #69, #100, #105, #110, #111, #113, #116.

### Testing
vahidlazio has iterated on prior review feedback. End-to-end testing with a real tunnel-node deployment will follow post-merge from @Feiabyte (volunteered in #61). Post-merge CI will exercise compile + full test matrix across all targets; any regression caught there gets a fast-follow fix.
2026-04-24 12:48:56 +03:00
therealaleph cb07311cf1 v1.2.4: range-parallel validation + port-collision guard + README Android note
- PR #78: validate Content-Range on 206 responses in the range-parallel
  path before stitching. Prevents malformed partials from being combined
  into a fake 200 OK. Invalid probe falls back to a normal single GET;
  invalid later chunks fall back to the validated probe response
  instead of shipping truncated/wrong data.

- PR #79: reject configs with listen_port == socks5_port at validation
  time (both config-load and UI form) instead of letting the second
  bind fail at runtime with a less clear error.

- README: add an explicit note about the Android 7+ user-CA trust
  limitation so future reporters (#74, #81, and the next dozen) find
  the answer in the docs instead of in a support thread. The previous
  "every app routes through the proxy" line was misleading — TUN
  captures all IP traffic but HTTPS still needs app-level trust of
  our MITM CA, which most non-browser apps don't grant.

Running through the new self-hosted CI pipeline. Warm rust-cache should
bring the full matrix in under ~7 minutes.
2026-04-23 20:52:06 +03:00
therealaleph df68a84a3c docs + sni: fix README quota (20k/day not 2M) and add scholar.google.com
- README: Persian FAQ was claiming ~2 million UrlFetchApp calls/day.
  Real free-tier quota is 20,000/day (100,000 on paid Workspace) per
  https://developers.google.com/apps-script/guides/services/quotas.
  Closes #63.

- DEFAULT_GOOGLE_SNI_POOL (Rust) + DEFAULT_SNI_POOL (Android): add
  scholar.google.com. Reported in #47 as another SNI that reliably
  passes DPI on MCI / Samantel where plain *.google.com subdomains are
  selectively blocked. Same mechanism as accounts.googl.com.
2026-04-23 15:34:57 +03:00
dazzling-no-more b90b003cbc feat: add google_only bootstrap mode (#62)
Second operating mode for users whose network already blocks
script.google.com and therefore cannot reach it to deploy Code.gs
in the first place. In google_only, the client runs only the
SNI-rewrite tunnel to *.google.com and the other Google-edge
suffixes that are already allowlisted; non-Google traffic falls
through to direct TCP. No script_id or auth_key is required. Once
Code.gs is deployed, the user switches to apps_script mode and
pastes the Deployment ID.

- config: Mode enum, relaxed validation when mode is google_only
- proxy_server: mode check in dispatch_tunnel; DomainFronter is now
  Option<Arc<_>> so it is not constructed in google_only
- desktop UI and Android app: Mode dropdown, Apps Script fields
  disable in google_only
- README: bootstrap subsection in English and Persian
- config.google-only.example.json
- version bump to 1.2.0 + changelog entry

Backward compatible with existing apps_script configs.
2026-04-23 15:28:47 +03:00
creep247 f45bc2f420 متن فارسی (#58)
* Update README.md

درست کردن متن فارسی

* Update README.md

درست کردن متن فارسی
2026-04-23 14:37:59 +03:00
Shin (Former Aleph) be698f4928 docs(android, fa): Persian translation of Android guide + README section (#35)
Mirrors docs/android.md for Persian-speaking users who land on the
Persian half of the README. Same structure — TOC, requirements table,
six-step setup, UI reference, known limitations, troubleshooting
table, log-collection snippet — rewritten in Persian RTL.

README's Persian section gets a new "اجرا روی اندروید" subsection
right before the FAQ, with a five-step quickstart and links to the
full Persian and English Android docs. The English preamble
(above the fold) also gains a فارسی link next to the English
Android-doc reference so bilingual readers see both options
immediately.

No code touched. Cross-references only.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 04:42:33 +03:00
Shin (Former Aleph) 91015b0594 v1.0.0: multi-arch Android APK + GitHub Actions release job + install docs (#30)
Version bump reflects the scope — a unified Rust core that now ships
for desktop (Linux/macOS/Windows) AND Android from the same crate.

Android changes:
- build.gradle.kts: ABI filters expanded to arm64-v8a + armeabi-v7a
  + x86_64 + x86. cargoBuild{Debug,Release} pass all four ABIs to
  cargo-ndk in a single invocation. normalizeTun2proxySo() walks every
  ABI dir now (was arm64-only).
- Release buildType signs with the debug keystore — no Play Store
  target, so signature identity doesn't matter, installability does.
  Gradle auto-provisions ~/.android/debug.keystore if absent, so CI
  runners inherit this without extra setup.
- versionName 1.0.0, versionCode 100 (room to bump monotonically).

CI:
- release.yml gets a dedicated `android:` job that sets up JDK 17,
  Android SDK/NDK 26, all four rust-android targets, installs
  cargo-ndk, runs assembleRelease, and uploads a single universal APK
  named `mhrv-rs-android-universal-v<version>.apk` into the same
  `dist/` collected by the release job downstream.
- `release:` job now gates on `needs: [build, android]` so tagging
  v1.0.0 triggers both build matrices before cutting the GitHub
  release.

Docs:
- docs/android.md — full 10-step install walk-through: APK sideload,
  Apps Script deployment (with "Advanced → Go to (unsafe) → Allow"
  reality check), config paste, SNI reachability test, MITM CA
  install with OEM-specific nav paths (Pixel / Samsung / Xiaomi),
  Start, troubleshooting common failure modes. Also documents the
  known limitations — Cloudflare Turnstile loops (inherent to the
  Apps Script egress IP pool), UDP/QUIC not tunnelled, IPv6 leaks,
  Apps Script daily quota — so users know what to expect before
  trying it on a site that won't work.
- releases/README.md — APK row added to the English and Persian
  tables, version bumped everywhere to v1.0.0.
- Top-level README — Android listed under Platforms with a link
  to docs/android.md.

Release artifact:
- releases/mhrv-rs-android-universal-v1.0.0.apk — 38 MB universal
  APK built locally from this tree. Installs + launches on API 24+.
  The CI job will regenerate it on tag push; this is the copy
  committed for users who can't reach GitHub Releases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 02:56:39 +03:00
therealaleph 62a75093ec docs: mirror upstream Code.gs locally (assets/apps_script/)
Two reasons to pin a copy in the repo:

1. Users on networks where raw.githubusercontent.com is intermittent
   can still get the deploy-ready file via a repo ZIP / clone.
2. The Apps Script relay protocol between mhrv-rs and Code.gs is
   informal — upstream changes can silently break us. Keeping a
   snapshot lets future-us diff against what we tested against
   when diagnosing protocol-drift bugs.

Fetched verbatim from:
  https://raw.githubusercontent.com/masterking32/MasterHttpRelayVPN/refs/heads/python_testing/apps_script/Code.gs

Credit stays with @masterking32. The assets/apps_script/README.md
next to it calls out that we don't modify this file — users deploy
it as-is into their own Google Apps Script project.

Updated the Setup Guide link in both the English and Persian
sections so offline / restricted-network users have a fallback path.
2026-04-22 18:37:01 +03:00
therealaleph 5371bfc7d5 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.
2026-04-22 16:34:40 +03:00
v4g4b0nd_0x76 11b8063a9e Merge branch 'main' into feat/google-ipv4-fetch 2026-04-22 13:54:41 +03:30
Mohammadreza Jafari 8e1ed523ac chor: update docs and read batch size and header checking from conf 2026-04-22 13:52:05 +03:30
Mohammadreza Jafari 2b3386db01 docs: update docs 2026-04-22 13:26:08 +03:30
therealaleph 660a693e43 docs: polished Persian guide — simpler wording, clean RTL layout, FAQ
Two user complaints:
- English words mixed inline in the Persian section were breaking the
  RTL text flow, making paragraphs hard to read.
- Language was too technical for non-developer users.

Fixes:

1. Every English / technical term is now wrapped in backticks
   (`Apps Script`, `MITM`, `SOCKS5`, `Deployment ID`, …). GitHub
   renders these as monospace LTR islands, which the browser's
   bidirectional text algorithm treats as embedded strong-LTR runs
   and doesn't let them flip the surrounding RTL paragraph direction.
2. Rewrote most paragraphs as shorter, plainer Persian sentences.
   Replaced jargon (run-time, on-the-fly, rewrite, trust store…)
   with everyday wording.
3. Converted dense prose into tables where it helped (download
   table by OS, config fields table, per-OS CA install table).
4. Added a 5-step walkthrough (script deploy → download → first
   run → config in UI → browser setup) that a non-technical user
   can follow top-to-bottom.
5. New 'How do I know it's working?' quick verification section.
6. New big FAQ at the bottom — covers the questions that actually
   come up: certificate install safety, how to remove the cert,
   how many Deployment IDs to use, YouTube / ChatGPT caveats,
   the GLIBC 2.39 issue, and CLI usage for power users.
7. Telegram pairing section reworded — explains the WHY first
   (Apps Script can't speak MTProto), then the one-line fix.
8. SNI pool editor flow written as numbered steps mirroring the
   actual UI buttons the user clicks.

English section unchanged.
2026-04-22 11:25:27 +03:00
therealaleph bcdb3e7803 v0.7.0: editable SNI rotation pool with reachability probes
New feature — users can now edit exactly which SNI names are rotated
through the outbound Google-edge tunnel, and probe each one's
reachability. Useful when an ISP selectively blocks individual Google
subdomains (e.g. mail.google.com in Iran at various times).

=== Data model ===

Config gains an optional 'sni_hosts' field:
  "sni_hosts": ["www.google.com", "drive.google.com"]

Precedence in domain_fronter::build_sni_pool_for():
  1. If sni_hosts is set & non-empty, use that list verbatim.
  2. Else, if front_domain is one of the default Google-edge names,
     auto-expand to {www, mail, drive, docs, calendar}.google.com.
  3. Else, use just [front_domain].

Empty / all-disabled list saves as None so the backend falls back to
the defaults instead of having zero names to rotate through.

=== New scan_sni module ===

probe_one(ip, sni) / probe_all(ip, snis) does, for each candidate:
  1. DNS lookup on the SNI (catches typos / non-existent names — Google
     GFE returns a valid wildcard cert for ANY *.google.com, so the
     TLS handshake alone can't tell apart a real name from gibberish).
  2. TCP connect to google_ip:443 (3s timeout).
  3. TLS handshake with the candidate SNI (3s timeout). RST mid-
     handshake signals DPI block.
  4. Small HTTP HEAD over the tunnel to confirm it's still speaking
     HTTP (catches weird misroutes).

Returns ProbeResult { latency_ms, error } per candidate.

=== New 'test-sni' CLI subcommand ===

  $ mhrv-rs test-sni
  Probing 5 SNI candidates against google_ip=216.239.38.120 ...
      SNI                  LATENCY  STATUS
      www.google.com        142 ms  ok
      drive.google.com      138 ms  ok
      mail.google.com            -  handshake RST (SNI may be blocked)
      ...
  Working: 3 / 5

Exit 0 if >=1 passed, non-zero otherwise. Uses the same probe logic
the UI uses.

=== UI editor ===

New 'SNI pool... (active/total)' button in the main form, styled with
a solid blue fill + white text so it's clearly actionable. Opens a
floating egui::Window (resizable, movable, closable) with:

  - Action bar: 'Test all' | 'Keep working only' | 'Enable all' |
    'Clear status' | 'Reset to defaults'
  - Scrollable list of rows, each: checkbox, monospaced name editor
    (230px), status cell (150px, 'ok 142 ms' green / 'fail <reason>'
    red / 'testing...' gray / 'untested' gray), per-row 'Test' and
    'remove' buttons
  - Bottom: text input + '+ Add' that auto-probes the newly added name
    immediately (instead of leaving it silently 'untested')

All rendered with ASCII status text instead of unicode check/cross
glyphs, since egui's default font doesn't ship them on some hosts
and they rendered as a missing-glyph box.

Changes only commit when the user hits Save config in the main window;
probe state is held in UiState::sni_probe so it survives opening and
closing the editor.

=== README ===

English + Persian 'SNI pool editor' sections with the two workflows
(UI button + 'sni_hosts' config field), plus a 'test-sni' line added
to the Diagnostics section. Feature list updated.
2026-04-22 03:25:28 +03:00
therealaleph 3f0bbfdab0 v0.6.0: performance pack — pool prewarm, SNI rotation, per-site stats, parallel dispatch
Tier-1 perf changes from the brainstorm, all on by default except where
they change semantics (parallel_relay is opt-in).

Connection pool pre-warm (domain_fronter.rs):
  On startup, open 3 TLS connections to Google edge in parallel and
  park them in the pool. First user request skips the ~300-500 ms
  handshake cost. Best-effort: warm failures are logged at debug and
  ignored. Triggered from ProxyServer::run() in a fire-and-forget
  tokio spawn.

SNI rotation (domain_fronter.rs):
  Replace the single sni_host String with a Vec<String> plus an atomic
  round-robin index. When front_domain is one of the known Google-edge
  subdomains, build_sni_pool() expands it to include the other four
  (www/mail/drive/docs/calendar.google.com), so outbound TLS connection
  counts get spread across names instead of concentrating on one. Custom
  front_domain values are preserved as the single entry (we can't verify
  siblings of a non-Google edge).

Expanded SNI-rewrite suffix list (proxy_server.rs):
  Added gstatic.com, googleusercontent.com, googleapis.com, ggpht.com,
  ytimg.com, blogspot.com, blogger.com to the list of domains routed
  directly via the Google-edge tunnel instead of through the Apps Script
  relay. Bigger bypass = less UA-locking, less quota burn on static CDN
  content.

Per-site stats (domain_fronter.rs + ui.rs):
  New HostStat struct {requests, cache_hits, bytes, total_latency_ns}
  tracked per URL host. Records on both cache hits and relay calls, not
  on SNI-rewrite bypasses (those never touch the fronter). UI renders
  a collapsible table under the existing stats grid with the top 60
  hosts sorted by request count, showing req count, cache hit %, bytes,
  avg latency ms.

Parallel script-ID dispatch (config.rs, domain_fronter.rs, ui.rs):
  New config field parallel_relay: u8 (default 0 = off). When >= 2 and
  there are enough non-blacklisted IDs, do_relay_with_retry fans out
  the request to N script instances concurrently via futures_util's
  select_ok, returns first success, cancels the rest. Kills long-tail
  latency when one Apps Script instance happens to be slow, at the
  cost of N× quota per request. UI exposes it as a DragValue 0-8.

TCP_NODELAY audit (proxy_server.rs):
  Added the missing set_nodelay(true) call on the SNI-rewrite outbound
  TCP stream. All six TcpStream::connect sites in the user traffic path
  now disable Nagle.

Expanded feature list in README, added futures-util dep, added unit
tests for extract_host and build_sni_pool.

Verified end-to-end locally:
- Pool pre-warm log line appears on startup: 'pool pre-warmed with 3
  connection(s)'.
- Static asset hit 3x: first = 2.2s (Apps Script), 2-3 = 6ms (cache).
- youtube.com / google.com: SNI-rewrite tunnel (unchanged).
- All 28 unit tests pass.

Deferred (not in this release, each needs its own cycle):
- uTLS / Chrome fingerprint mimicry (TLS stack swap)
- QUIC/HTTP3 transport (new transport)
- ETag / If-None-Match revalidation (needs cache schema change)
- JSON envelope gzip on request (needs Code.gs change)
- Firebase Cloud Functions as alt backend (new architecture)
- MSS clamp / TCP Fast Open (platform-specific, marginal)
2026-04-22 02:52:36 +03:00
therealaleph 6c5b62e5e6 v0.5.1: static musl builds for OpenWRT (amd64 + arm64)
A user on OpenWRT x86_64 reported the linux release doesn't run there —
root cause was glibc vs musl mismatch (our gnu binary was looking for a
dynamic linker that doesn't exist on router userlands). Add two musl
targets that produce fully static PIE binaries:

- x86_64-unknown-linux-musl  -> mhrv-rs-linux-musl-amd64.tar.gz
- aarch64-unknown-linux-musl -> mhrv-rs-linux-musl-arm64.tar.gz

CI uses the messense/rust-musl-cross docker images (better-maintained
than cargo-zigbuild with a pinned zig, which has version regressions
on the ar wrapper between 0.13 and 0.16).

Locally verified:
- both archs cross-compile green in docker
- resulting x86_64 binary (3.3 MB) runs in an alpine:latest container,
  --version / --help work, no dynamic lib requirements

The musl archive skips the UI (routers are headless) and swaps run.sh
for a procd init script (assets/openwrt/mhrv-rs.init) expecting the
binary at /usr/bin/mhrv-rs and config at /etc/mhrv-rs/config.json.

Side effect: switched tokio-rustls to default-features=false + ring
(was pulling aws-lc-rs transitively, which can't easily cross-compile
for musl). The main crate already uses ring explicitly, so no runtime
behavior change.

README gets a 'Running on OpenWRT (or any musl distro)' section in
both English and Persian with scp + procd enable/start recipe.
Closes #2.
2026-04-22 02:29:26 +03:00
therealaleph e575bf6bf4 v0.5.0: optional upstream SOCKS5 for non-HTTP traffic (Telegram et al.)
The Apps Script relay is HTTP-only, and the SNI-rewrite tunnel only
works for Google-hosted domains — so MTProto / IMAP / SSH / anything
else used to drop to a direct-TCP passthrough, which provides zero
circumvention. Users behind a DPI that blocks Telegram saw constant
disconnect/reconnect loops because the raw TCP ran right into the
block.

Fix: add an optional 'upstream_socks5' config field. When set, the
raw-TCP fallback chains the flow into that SOCKS5 proxy (typically a
local xray / v2ray / sing-box with a VLESS / Trojan / Shadowsocks
outbound to your own VPS) instead of connecting directly. The whole
rest of the pipeline is unchanged:

- HTTP / HTTPS still MITMs and relays via Apps Script
- SNI-rewrite suffixes (google.com, youtube.com, …) still hit the
  direct Google-edge tunnel (so YouTube stays fast)
- Only the raw-TCP bucket (Telegram MTProto, SSH, IMAP, …) gets the
  new upstream chain

Changes:
- config.rs: add Option<String> upstream_socks5 field
- proxy_server.rs: thread it through RewriteCtx; rewrite
  plain_tcp_passthrough to call a new socks5_connect_via() helper
  when configured, with graceful fallback to direct
- ui.rs: new 'Upstream SOCKS5' input with tooltip + placeholder,
  ConfigWire round-trip
- README.md: new 'Pair with xray for Telegram' section (EN + FA)
  with the architecture diagram and example config

Verified end-to-end in Docker: xray with the user's working VLESS
Reality config, mhrv-rs with upstream_socks5 pointing at it.
- HTTPS via mhrv-rs SOCKS5: origin = Google IP (Apps Script path) ✓
- Raw TCP to 3 Telegram DCs + api.telegram.org: all SOCKS5 rep=0, log
  shows 'tcp via upstream-socks5 127.0.0.1:50529 -> …' ✓
- youtube.com / google.com: 'SNI-rewrite tunnel' (unchanged) ✓
- Real Telegram Desktop stayed connected cleanly (user-confirmed).
2026-04-22 01:49:21 +03:00
therealaleph 54d317ae2c docs: add UI screenshot + releases/ folder with explainer
- docs/ui-screenshot.png: running UI with live traffic stats
- releases/README.md: documents the in-repo prebuilt binaries for users
  who cannot reach the GitHub Releases page (English + Persian)
- README: embed the screenshot in the 'What's in a release' section
2026-04-21 22:37:42 +03:00
therealaleph 4d1600a349 readme: explain the MITM CA step (local CA, private key stays local, how to revoke)
Users reasonably get nervous when an installer adds a root CA. Spell
out what the install actually does and does not do, in both the English
and Persian sections:

- CA keypair is generated locally in the user-data dir
- Only the public cert is added to the trust store
- Private key never leaves the machine; no network side is involved
- Clear revocation steps
- Manual CLI fallback if the launcher isn't wanted
- Firefox NSS note in case certutil best-effort misses
2026-04-21 22:27:49 +03:00
therealaleph 2dfaef2048 readme: full rewrite for v0.4.x (UI + launchers, sharper structure)
- Top-of-page anchor is راهنمای فارسی (not URL-encoded)
- Document the launcher scripts as the recommended first run
- Update CA path (user-data dir, not ./ca)
- Mirror the English and Persian sections feature-for-feature
- Add a short security posture section
- Wrap the Persian block in <div dir=rtl> for correct rendering
2026-04-21 22:21:14 +03:00
therealaleph 899ef06f4a v0.4.1: launcher scripts (run.sh / run.command / run.bat)
First run needs the CLI to install the MITM CA into the system trust
store (sudo/admin prompt), which the UI alone can't do reliably from a
double-click. Add a small launcher for each platform that runs the CLI
with --install-cert once, then starts the UI. Each release archive now
contains a run.* script alongside the binaries.
2026-04-21 22:17:25 +03:00
therealaleph e4fe2b5939 v0.4.0: add cross-platform desktop UI (egui)
New bin 'mhrv-rs-ui' behind the 'ui' feature flag. CLI users pay
zero egui compile cost; UI users get a single static binary.

UI features:
- Config form (Apps Script ID, auth key, Google IP, front domain,
  ports, log level, verify_ssl)
- Start/Stop buttons that spawn the proxy on a dedicated tokio thread
- Live stats (relay calls, failures, cache hit rate, bytes relayed,
  blacklisted scripts) polled every ~700ms
- Test button (end-to-end relay probe)
- Install CA / Check CA buttons
- Recent log panel (last 200 lines)
- Dense, dark, utility-look: no emojis, no cards, no gradients

Architecture:
- Refactored crate into lib + two bins (mhrv-rs, mhrv-rs-ui).
  src/lib.rs exposes all modules, main.rs uses them via 'use mhrv_rs::...'
- New src/data_dir.rs: platform-appropriate user data dir
  (~/Library/Application Support/mhrv-rs on macOS,
   ~/.config/mhrv-rs on Linux, %APPDATA%\mhrv-rs on Windows).
  CLI falls back to ./config.json for backward compat.
- CA moves to {data_dir}/ca/ca.crt (was ./ca/ca.crt).
- UI background thread owns the tokio runtime and proxy handle;
  communicates with UI via std::mpsc commands + Arc<Mutex<UiState>>.
- macOS .app bundle: assets/macos/Info.plist template + build-app.sh
  that assembles .app from the binary. Bundled into release zips.
- CI: Linux system libs (libxkbcommon, libwayland, libxcb*, libx11,
  libgl, libgtk-3) installed on Ubuntu runners for eframe. aarch64
  Linux UI is best-effort cross-compile. Windows MinGW, macOS native.

25 lib tests still pass. 5MB release UI binary on macOS.
2026-04-21 21:36:52 +03:00
therealaleph f5397bef43 v0.3.0: SOCKS5 listener + smart TLS/HTTP/plain-TCP dispatch
Ports the SOCKS5 + fallback-chain design from @masterking32's
MasterHTTP-WithSOCKS branch so xray / Telegram / app-level TCP
clients work through this proxy.

Changes:
- New SOCKS5 listener on listen_port+1 (configurable via socks5_port)
  - RFC 1928 CONNECT handshake (v5, no-auth, ATYP IPv4/domain/IPv6)
  - Shared smart dispatch with the HTTP-CONNECT path
- Unified dispatch_tunnel() used by both CONNECT entry points:
  1. If host matches SNI-rewrite suffix or hosts override: go direct
     to google_ip via the MITM+TLS tunnel (fast path for google.com,
     youtube, etc.)
  2. Peek the first byte (300ms timeout for server-first protocols):
     - 0x16: TLS client hello -> MITM + relay via Apps Script (scheme=https)
     - HTTP method signature: HTTP relay via Apps Script (scheme=http)
     - Anything else or timeout: plain TCP passthrough to the target
- handle_mitm_request() now takes a scheme arg (http/https) so the
  same code path handles both MITM'd HTTPS and port-80 plain HTTP
- New plain_tcp_passthrough helper: bidirectional TCP bridge used as
  the final fallback (covers MTProto / raw TCP / server-first protos)

Config:
- Added optional socks5_port field; defaults to listen_port+1

README:
- Added browser vs xray/Telegram instructions under 'Step 6'

Live-tested: HTTP proxy, HTTP proxy -> HTTPS, SOCKS5 -> HTTP,
SOCKS5 -> HTTPS, Google search via SNI-tunnel (now returns full
JS page) all pass.
2026-04-21 20:29:24 +03:00
therealaleph 343def4c88 v0.2.2: route google.com via SNI-tunnel to avoid bot UA
Context: user reported Google search showing no-JS fallback page
('JS is off apparently'). Root cause is Apps Script's fixed
'Google-Apps-Script; beanserver' User-Agent that UrlFetchApp.fetch
does not let you override. Google detects the bot UA and serves
the degraded HTML.

Fix: add google.com to SNI_REWRITE_SUFFIXES so google.com requests
bypass Apps Script entirely and go direct to Google's edge via the
MITM+TLS tunnel. Real browser UA is sent; full JS version is served.

Also documented this and other inherent limitations (WebSockets,
2FA 'unknown device', video chunk slowness, brotli stripping) in
the README under 'Known limitations' in English + Persian so users
aren't surprised. These are platform limits of Apps Script, not
bugs -- same issues exist in the original Python project.
2026-04-21 19:58:06 +03:00
therealaleph ea5c6ca9a4 bump to v0.2.0 + update README
Features added since v0.1.0 (all live-tested against real Apps Script):
- Response cache (FIFO+TTL, Cache-Control aware, 50MB cap)
- Request coalescing for concurrent identical GETs
- Auto-blacklist failing scripts on 429/quota (10m cooldown)
- SNI-rewrite MITM tunnels for YouTube/googlevideo/doubleclick/etc.
- Gzip response decoding (was breaking all requests in v0.1.0)
- Firefox NSS cert install (best effort via certutil)
- Periodic stats log (60s)
- 'mhrv-rs test' subcommand (end-to-end relay probe)
- 'mhrv-rs scan-ips' subcommand (28 Google IPs, sorted by latency)
- Script IDs masked in logs

Intentionally skipped with rationale (documented in README):
- HTTP/2 multiplexing: coalesce+pool already parallelizes enough
- Request batching: marginal gain over current async pool
- Range-based parallel download: video bypasses via SNI-rewrite

25 tests pass. 2.5 MB stripped release binary.
2026-04-21 18:37:30 +03:00
therealaleph 00e0d411fc fix Code.gs link: it's on the python_testing branch 2026-04-21 18:08:47 +03:00
therealaleph 2dd8be72ca initial release: Rust port of MasterHttpRelayVPN apps_script mode
Faithful port of @masterking32's MasterHttpRelayVPN. All credit for
the original idea, protocol, and Python implementation goes to him.

Implemented:
- Local HTTP proxy (CONNECT + plain HTTP)
- MITM with on-the-fly per-domain cert generation via rcgen
- CA auto-install for macOS / Linux / Windows
- Apps Script JSON relay, protocol-compatible with Code.gs
- TLS client with SNI spoofing (connect to Google IP, SNI=www.google.com,
  inner HTTP Host=script.google.com)
- Connection pooling (45s TTL, max 20 idle)
- Multi-script round-robin for higher quota
- Header filtering (strips connection-specific + brotli)
- Config-driven, JSON schema matches Python version

Deferred (TODOs in code):
- HTTP/2 multiplexing
- Request batching / coalescing / response cache
- Range-based parallel download
- SNI-rewrite tunnels for YouTube/googlevideo
- Firefox NSS cert install
- domain_fronting / google_fronting / custom_domain modes
  (mostly broken post-Cloudflare 2024, not a priority)

13 unit tests pass, 2.4MB stripped release binary.
2026-04-21 18:03:03 +03:00