mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-18 08:34:35 +03:00
v1.9.28
82 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6d9bc2be6d |
Merge pull request #143 from dazzling-no-more/fix/split_tunneling_android
fix(android): app splitting ONLY mode don't mix allowed/disallowed apps |
||
|
|
cc04558437 |
android: publish per-ABI APKs in addition to universal (fix #136)
GitHub Releases is filtered from inside IR, and the 50 MB universal APK
is a bottleneck for users on slow/unstable censorship-tunnel paths that
can't reliably pull that much data. Per-ABI APKs are ~18–23 MB each —
small enough to succeed where the universal fails.
Build changes:
- android/app/build.gradle.kts: enabled `splits { abi { ... } }` with
`isUniversalApk = true`, producing five release APKs:
app-universal-release.apk ~53 MB (all 4 ABIs)
app-arm64-v8a-release.apk ~21 MB (95%+ of modern devices)
app-armeabi-v7a-release.apk ~18 MB (older 32-bit ARM)
app-x86_64-release.apk ~23 MB (emulators, Chromebooks)
app-x86-release.apk ~22 MB (legacy 32-bit Intel)
abiFilters is retained for the universal build; splits.abi layers on
per-ABI outputs without removing it.
- .github/workflows/release.yml: rename step now copies all 5 APKs to
dist/ under versioned names (mhrv-rs-android-{abi}-v{VER}.apk),
logs a warning if any per-ABI APK is missing, and hard-fails only if
the universal is missing. Universal keeps its existing download path
and filename so Telegram mirrors / previous-version update prompts
keep working.
The release + telegram aggregation jobs downstream don't need changes —
they already use `files: dist/*` and `mhrv-rs-android-universal` artifact
name respectively.
Local build verified: clean assembleRelease produces all 5 APKs with the
expected size ratios (arm64-v8a is 41% the universal size).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
4ccd864e72 | fix(android): app splitting ONLY mode — don't mix allowed/disallowed apps | ||
|
|
d01e9f0f2f |
v1.2.14: Usage today (estimated) card + Persian guide localization
Adds a daily-budget visualization for users worried about hitting the
Apps Script free-tier quota (20,000 UrlFetchApp calls/day).
Usage today card (desktop + Android):
- today_calls / today_bytes / today_key / today_reset_secs atomics on
DomainFronter, hooked into the bytes_relayed fetch_add path so we only
count successful relays (matching what Google actually billed)
- Daily rollover at 00:00 UTC, std-only date math (Hinnant's
civil_from_days) — no chrono/time dep pull
- StatsSnapshot extended with the four new fields + to_json() for the
Android JNI bridge
- Desktop UI renders the card right under the existing Traffic stats
with a hyperlink to https://script.google.com/home/usage for the
authoritative Google-side number
- Android UI renders the same card via Compose, polling
Native.statsJson(handle) once a second only while the proxy is up,
with an Intent(ACTION_VIEW, …) opening the dashboard URL
JNI / state plumbing:
- New Java_…_statsJson reads the Arc<DomainFronter> kept in slot_map
- VpnState.proxyHandle StateFlow so HomeScreen knows which handle to
poll without poking into the service's internal state
- MhrvVpnService publishes the handle on start, zeroes on teardown
Persian localization:
- HowToUseBody (5-step guide + Cloudflare Turnstile note) was
hardcoded English even when locale=FA. Ported to a string resource
with a full FA translation in values-fa/strings.xml. Persian users
no longer drop to English at the bottom of the screen.
Also lands the deferred Android ConfigStore.kt wiring for
passthrough_hosts (commit
|
||
|
|
a05fc5d612 |
v1.2.13: Android Full Tunnel Mode requires credentials (fix #124)
Bug fix release. My v1.2.12 merge of Mode::Full bypassed the deployment-ID + auth-key check on Android, but Full mode talks to CodeFull.gs on Apps Script and needs those same credentials. Users selecting "Full tunnel (no cert)" with empty fields would see the VPN service bail silently instead of surfacing a clear "config incomplete" error. Vahidlazio's fix changes the gate from `mode == APPS_SCRIPT` to `mode != GOOGLE_ONLY` and removes the Mode.FULL bypass in the Start button's enabled-state. Also includes a UX refactor of the Deployment IDs editor (per-row rows with add/remove buttons instead of raw newline-separated text), making multi-deployment setups easier to manage on Android — useful now that Full Tunnel Mode users routinely scale to 5+ deployments per their Google accounts. Android-only diff; Rust side is byte-identical to v1.2.12. |
||
|
|
f9f4845567 |
fix: Android full tunnel mode requires credentials + deployment IDs UI refactor (#124)
Real bug I introduced in #94: Full mode was skipping the credential check that apps_script mode enforces, but Full mode does talk to CodeFull.gs on Apps Script and needs the same auth_key + deployment ID. Users flipping to Full mode with empty fields would silently fail. Two sites fixed: - MhrvVpnService.kt — changed `mode == APPS_SCRIPT` gate to `mode != GOOGLE_ONLY` - HomeScreen.kt — removed the `cfg.mode == Mode.FULL` bypass in the Start button's enabled-state Also includes a UX improvement for the Deployment IDs editor (per-row field with add/remove buttons instead of raw newline-separated text), which makes multi-deployment setups easier to manage on Android. Rust-side 75 tests still green, Kotlin compiles clean. Android-only diff so no Rust CI impact. |
||
|
|
9e2b8e5f3e |
v1.2.12: Full Tunnel Mode (#94 merged)
Rollup of PR #94 — Mode::Full dispatch + batch tunnel client. Ships the long-awaited no-MITM path that was the motivating fix for half the open issues this week. User-facing: add `"mode": "full"` to config.json, deploy CodeFull.gs as a second Apps Script alongside your existing one, deploy tunnel-node (tunnel-node/README.md) on a VPS, and traffic is tunneled end-to-end: client → mhrv-rs → script.google.com → your tunnel node → destination. Browser speaks TLS directly with the destination; we never see plaintext. No CA needed on the client device. Android side gets a "Full tunnel (no cert)" dropdown option; toggling it writes `"mode": "full"` to config.json. Safety: Mode::AppsScript and Mode::GoogleOnly dispatch paths are unchanged — Full mode is an additive branch at the top of dispatch_tunnel. Existing users on the default apps_script mode see zero behaviour change. Testing status: compiles clean on all 10 CI targets; 75 tests pass (+2 new config-validation tests for Full mode); end-to-end real-VPS testing will come post-release from @Feiabyte and others who opt in. Any Full-mode regression gets a fast-follow fix. |
||
|
|
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. |
||
|
|
259431b44f |
v1.2.11: x.com URL truncation now fires for www.x.com (fix #64)
Single-bug release. Unblocks x.com browsing for users whose browsers resolve to www.x.com rather than bare x.com — i.e. essentially everyone using Firefox / Chrome / Safari. Previous releases still advertised the URL-truncation fix as working but it only matched exact Host: x.com, which never happens in real traffic. v1.2.11 widens the matcher to x.com + *.x.com so www.x.com, api.x.com, and any future x.com subdomain all get the shortened URL through Apps Script's URL length cap. |
||
|
|
29777ce5b2 |
v1.2.10: proxy Stop actually stops now (fix issue #99)
Single-focus release. The Stop button in the UI previously only
stopped new connections from being accepted — in-flight clients kept
running on the old DomainFronter, which meant:
- Pages kept loading after Stop (users thought they'd stopped)
- Auth-key changes didn't take effect for domains with a live
keep-alive to the proxy
- Apps Script quota could still be consumed post-Stop
Fix (
|
||
|
|
1d2eb19295 |
v1.2.9: fix UI build for youtube_via_relay (v1.2.8 CI abandoned)
v1.2.8 tagged cleanly but CI failed compiling mhrv-rs-ui with:
error[E0063]: missing field `youtube_via_relay` in initializer of
`mhrv_rs::config::Config`
When I added the youtube_via_relay field to the main Config struct
in
|
||
|
|
6fdbfe3966 |
v1.2.8: real-IP-leak fix + youtube_via_relay + scan_sni hardening
Rollup of four merged fixes since v1.2.7: - security: strip identity-revealing forwarding headers in the Apps Script relay path. Closes the XFF leak vector from issue #104 — users chained behind xray/v2rayNG or running browser extensions that inject X-Forwarded-For / Forwarded / Via / CF-Connecting-IP etc. would previously have those forwarded to the origin via the relay. Now stripped to 16 header variants with a regression test. - proxy: new `youtube_via_relay` config toggle (#102). Routes YouTube family suffixes through Apps Script instead of the SNI-rewrite tunnel. Trades SafeSearch-on-SNI for Apps Script's fixed User-Agent + quota cost. Off by default. - scan_sni: decode chunked dns.google DoH responses (#97, from @freeinternet865). Without this, PTR lookups always failed and scan-sni discovered zero domains. - scan_sni: verify dns.google TLS with webpki roots (#98, from @freeinternet865). The DoH request is a normal public HTTPS call — an on-path MITM should not be able to forge PTR answers and poison the suggested SNI pool. 73 tests pass (up from 67 — three new chunked-decode tests + one XFF-filter + two youtube_via_relay branches). |
||
|
|
0a29cf0740 |
v1.2.7: SNI cert fix mirrored to Android + tunnel-node scaffold (via #92 + #93)
- Android DEFAULT_SNI_POOL: mirror the Rust-side fix from #92 — accounts.googl.com replaced by accounts.google.com. Same cert-SAN mismatch that was failing every Nth rotation in the Rust client affected the Android user's sniHosts population; both pools need to stay in sync by design. - Release rolls up PR #92 (cert fix) and PR #93 (tunnel-node + CodeFull.gs scaffolding). PR #93 adds a standalone binary under tunnel-node/ plus an Apps Script companion; no main-crate changes, so this is a zero-risk merge. Users who want to deploy a tunnel node can start today. The dispatch that activates `mode: full` is still in review in PR #94. |
||
|
|
658e72fe0d |
v1.2.6: rust-cache bin pruning fix + PR #83 scan-sni
v1.2.4 and v1.2.5 both cut clean tags but CI failed downstream for different self-hosted reasons: - v1.2.4 failed on parallel apt-lock race (fixed) - v1.2.5 failed with "TOML parse error at line 5 column 9" because rust-cache v2's default cache-bin=true prunes $CARGO_HOME/bin of any binary not installed via `cargo install`. `rustup` itself is installed by rustup-init, not cargo install, so it got flagged as "unknown" and deleted on cache save. Next job hits the cargo symlink that points at a missing rustup, which resolves somehow to a very old cargo that can't parse our Cargo.toml. Fix: - Set `cache-bin: "false"` on every Swatinem/rust-cache@v2 call. We still cache target/ + registry (the big win), just not bin/. Binaries are stable across runs on our self-hosted box anyway. - Reinstalled rustup inside each per-runner CARGO_HOME on the server to recover from the broken state. Also in this release: - PR #83: new `mhrv-rs scan-sni` subcommand. Pulls Google's published IP ranges, does PTR lookups via dns.google on each IP, filters to Google-related hostnames, then TLS-probes each discovered SNI against the configured google_ip to see which ones bypass DPI. Useful for rebuilding a working SNI pool on a new ISP. Adds the `url` crate dep. Same user-facing code as v1.2.4/v1.2.5 (PRs #78, #79, README Android note) plus PR #83 and the CI fixes on top. |
||
|
|
af44abbcd3 |
v1.2.5: CI self-hosted apt-lock fix (v1.2.4 release was incomplete)
v1.2.4 tagged cleanly but its CI failed — parallel Linux matrix jobs
on the self-hosted runners all raced on `/var/lib/apt/lists/lock` and
failed the `sudo apt-get install` step within ~20s. v1.2.4's release
job therefore skipped and no assets were published.
Fix:
- Pre-installed every apt dependency the workflow needs on both
self-hosted runners (eframe system libs, gcc-aarch64-linux-gnu,
gcc-arm-linux-gnueabihf).
- Seeded per-runner cargo linker configs at
/home/ghrunner/cargo-{01,02}/config.toml so the "echo
[target.xxx] linker = ..." workflow step is also unnecessary.
- Gated the "Install Linux eframe system deps" and the two cross-
compile-toolchain steps on `runner.environment == 'github-hosted'`
so only hosted runners call apt-get; self-hosted runners skip the
whole thing and use pre-installed tooling.
Re-tagging as v1.2.5 since v1.2.4 is an abandoned tag (git tag exists
but no GitHub Release was cut for it).
Same code changes as what v1.2.4 was meant to ship: PR #78 range-
parallel validation, PR #79 port-collision rejection, README note
on Android 7+ user-CA trust.
|
||
|
|
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. |
||
|
|
15e3e38745 |
v1.2.3: move CI to self-hosted runners + rust-cache
Linux / Android / mipsel build jobs now run on two self-hosted runners on a Hetzner 8-core / 31 GB Ubuntu 24.04 box with Rust, Android SDK+NDK r26c, all cross-compile toolchains and Docker pre-installed. macOS and Windows still run on GitHub-hosted — we don't self-host those OSes and the free minutes on a public repo are plenty. Adds Swatinem/rust-cache@v2 to every cargo-using job so target/ + cargo registry survive between runs. With warm caches the Linux jobs take ~1min each and the Android job ~3-4min; cold runs are ~9min for Android and ~2min for everything else. Release wall time before this change was ~13m consistently; it should now sit around 6-7m. No new user-facing code in this release — primarily an infra change exercised by an actual tag-push so we verify the full pipeline works end-to-end from the new runners. |
||
|
|
e48a8f6add |
v1.2.2: Android Start crash fix + google_ip preservation + chromewebstore SNI
Three user-facing fixes: - Android Start crash in google_only mode (#73): every early-return path in startEverything now satisfies Android 8+'s foreground-service contract by calling startForeground before stopSelf. Previously if you opened the app, selected google_only mode, and tapped Connect without filling deployment ID + auth key (which google_only doesn't need anyway), the service crashed with ForegroundServiceDidNotStartInTimeException. Also gated the deployment-ID requirement on mode == APPS_SCRIPT. - google_ip auto-overwrite on Start (#71): some carriers serve poisoned DNS for www.google.com that resolves but refuses TLS, clobbering working IPs users had manually set. DNS lookup now only fires when the field is blank — manual configs are preserved across Connect. Explicit "Auto-detect" button still refreshes on demand. - chromewebstore.google.com added to DEFAULT_GOOGLE_SNI_POOL and DEFAULT_SNI_POOL (#75). Same family as the rest of the pool — wildcard cert, GFE-hosted. |
||
|
|
1d5d13d63d |
v1.2.1: IP-literal fast-fail + more SNIs + x.com GraphQL fix + Android SNI paste
Rollup of the three upstream-Python ports plus an Android UX polish: - plain_tcp_passthrough: 4s connect timeout for IP literals (10s for hostnames). Halves Telegram DC-rotation latency when the current DC is DPI-dropped. - DEFAULT_GOOGLE_SNI_POOL / DEFAULT_SNI_POOL: +maps, chat, translate, play, lens.google.com. More fingerprint spread, and maps/play pass DPI on some carriers where shorter *.google.com names don't. - handle_mitm_request: x.com GraphQL URL truncation — strip everything after the first & when the path matches /i/api/graphql/.../?variables=. x.com's variables+features+fieldToggles blob overflows Apps Script's URL cap; `variables=` alone renders the timeline. - Android SNI editor: paste-and-add now accepts a full list separated by whitespace / commas / newlines, dedupes, and merges with existing selection. Closes the "add them all at once" ask from #47. - rlimit.rs: fence the example error log in a `text` code block so rustdoc stops trying to compile it. |
||
|
|
08fe6911b3 |
port upstream connection-quality fixes (masterking32/MasterHttpRelayVPN)
Three ports from the upstream Python repo — all straightforward wins for user-facing connection reliability: - plain-tcp: 4s connect timeout for IP literals (10s for hostnames). Ported from upstream 7b1812c. When Telegram MTProto (or any protocol that CONNECTs to a raw IP) hits a DPI-dropped DC, failing fast lets the client rotate to the next DC roughly twice as quickly. Users previously sat on "connecting..." for nearly a minute walking DC1→DC3. - SNI rotation pool: add maps/chat/translate/play/lens.google.com to both the Rust DEFAULT_GOOGLE_SNI_POOL and the Android DEFAULT_SNI_POOL. Ported from upstream 57738ec. Extra fingerprint spread plus a couple of SNIs (maps, play) that reliably pass DPI where shorter *.google.com names don't. - x.com GraphQL URL truncation: when the path matches /i/api/graphql/<hash>/<op>?variables=..., drop everything from the first `&` onward. The combined variables+features+fieldToggles query string regularly exceeds Apps Script's URL length cap and returns a generic relay error; `variables=` alone is enough for x.com's timeline to render. Ported from upstream 2d959d4. No version bump — these are low-risk infrastructure patches; they'll fold into the next release. |
||
|
|
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. |
||
|
|
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. |
||
|
|
5a108f73cb |
v1.1.5: merge upstream safety fixes + Telegram default = file + link only (#60)
Contains the three safety fixes from PRs #48/#49/#50 and the Persian README RTL polishing from #58, all squashed into main. Merge details already in their individual PR comments; summary: #48: reject truncated Content-Length relay responses (previously silently accepted whatever bytes arrived before EOF) #49: reject truncated or malformed (missing CRLF) chunked-encoding relay responses (same class of silent-acceptance bug) #50: restrict the SNI-rewrite tunnel dispatch to port 443. Plain HTTP (:80) targets that happened to match google.com / hosts override were being steered into the TLS tunnel and blocking waiting for a ClientHello that would never arrive. #58: trailing-whitespace line-breaks on Persian bullet lists in README so the RTL rendering doesn't collapse consecutive items into a single paragraph. Test suite grew from 54 to 58 passing (three new negative tests for the relay-reader correctness fixes + one SNI-rewrite port filter). Telegram CI notify default switched to file-plus-link: - script gains a `--with-changelog` flag; default OFF - workflow only passes it when `vars.TELEGRAM_INCLUDE_CHANGELOG=true` - every routine release now posts just the APK + short caption (title + SHA-256 + repo URL + release URL) with no long body To include bullets for a given release again: gh variable set TELEGRAM_INCLUDE_CHANGELOG --body true The existing `vars.TELEGRAM_NOTIFY_ENABLED` job-level gate remains — changelog toggle is orthogonal to enable/disable. Also closes PR #55 without merging; ads/analytics domains were being lumped under a YouTube-specific toggle, and the PR committed per- machine \`.cargo/config.toml\` + zig-cc cross-compile helpers that would have broken CI on actual Windows / macOS runners. |
||
|
|
8d2f90b0a7 |
v1.1.4: YouTube video streaming — expanded SNI-rewrite list + parallel Range fetcher (#56)
Users of the upstream Python port
(github.com/masterking32/MasterHttpRelayVPN) reported that YouTube
videos render fine through theirs while the Rust port stalls. Diff
against the Python source exposed two substantive gaps we were
missing:
1. SNI-rewrite list was much shorter than upstream. Added:
gvt1.com, gvt2.com — Google Video Transport CDN (YouTube
video chunks + Chrome auto-updates +
Play Store downloads)
doubleclick.net — ads
googlesyndication.com
googleadservices.com
google-analytics.com
googletagmanager.com
googletagservices.com
fonts.googleapis.com — already covered by the googleapis.com
suffix but mirrored explicitly for clarity
These are all on Google's GFE IP pool, so they route over the
existing SNI-rewrite tunnel (direct to `google_ip` with SNI
rewritten) instead of the quota-limited Apps Script relay.
2. No range-parallel download path. Apps Script's per-call latency
is ~flat (~1-2s regardless of payload), so a 10 MB single GET
takes ~10s round-trip; the player times out or stutters. Upstream
Python's `relay_parallel` probes with Range: bytes=0-262143, and
if the origin supports ranges, fetches the rest in parallel
256 KB chunks (up to 16 concurrent). Ported that logic as a new
`DomainFronter::relay_parallel_range` method, called from both
MITM-HTTPS and plain-HTTP handlers for GETs without a body. Rust
implementation uses `futures::stream::buffered` for ordered
bounded-concurrency fan-out; cache layer already skips Range
requests (added defensive check in relay() too).
The existing single-script fan-out (`parallel_relay` config) is
complementary — it races N script IDs for each individual chunk,
where the range-parallel path slices the overall download. Both are
active simultaneously when both are configured.
Helper functions for HTTP parsing (split_response,
parse_content_range_total, rewrite_206_to_200, assemble_full_200)
mirror the Python equivalents.
No behaviour change for non-GET requests; no cache-correctness
changes for GETs that don't return 206.
|
||
|
|
5a5139f6ea |
v1.1.3: portable-atomic polyfill so mipsel-softfloat compiles (#46)
v1.1.2 reached cargo build inside the mipsel docker this time (YAML-fold bug finally out of the way) and surfaced the real underlying problem: MIPS32 has no native 64-bit atomic instructions, so std::sync::atomic::AtomicU64 doesn't exist on mipsel-unknown-linux-musl. Three call sites (DomainFronter stats counters + the request-cache) failed to resolve the import. Fix: depend on `portable-atomic` with the `fallback` feature and import AtomicU64 from there instead of std. The API is identical (same associated methods, same Ordering accepted), so the two touched files change only the `use` line. On 64-bit targets portable-atomic compiles down to the native 64-bit atomic insns with no overhead; on MIPS32 it uses a global spinlock, which is fine for counter increments that happen a few times per relay. Cache.rs and domain_fronter.rs both updated. No other callers of AtomicU64 in non-cfg-gated code (android_jni.rs has it but is gated `#![cfg(target_os = "android")]`, so mipsel-linux-musl never sees it). `cargo test --lib` / `cargo build` still pass on host. |
||
|
|
383bea008e |
v1.1.2: actually-green mipsel-softfloat (YAML comment-fold bug) (#44)
v1.1.1 still failed the mipsel CI matrix for a non-obvious reason.
The `Build CLI (mipsel-softfloat via docker)` step passed a
multi-line argument to `sh -c "..."` with `\` line continuations
and inline `#` comments:
sh -c "set -eux; \
# The image ships with a pre-installed nightly ... \
rustup toolchain uninstall ... \
..."
YAML's `run: |` block-scalar folds that into a single line on the
shell side — backslash-newline collapses become spaces. The
payload handed to `sh -c` becomes one long line in which the
first `#` comments out everything that follows on that line, so
the only command that actually ran inside the container was
`set -eux;`. Everything after it was a comment. The container
exited successfully (set -eux + empty; is a zero-exit no-op),
the `target/` directory never got created, and the post-docker
`sudo chown -R "$(id -u):$(id -g)" target` failed with
chown: cannot access 'target': No such file or directory
Process completed with exit code 1.
which fooled me into thinking the toolchain logic failed, when
actually NO toolchain logic ran at all.
Fix: use bash with a single-quoted multi-line script. Single
quotes preserve newlines literally, so `#` stays a
line-terminating comment rather than collapsing. Heredoc-style
formatting; same commands as before.
No other changes. Version bumps only (Cargo + Android versionCode/
versionName). Telegram notify stays off via the repo-variable
gate we added yesterday.
|
||
|
|
a7b63ee53a |
v1.1.1: accounts.googl.com in SNI pool + mipsel-softfloat lands green (#43)
SNI rotation pool gains `accounts.googl.com` (issue #42). Reporter confirmed it passes DPI on Samantel and MCI — Iranian carriers that selectively block some of the longer google.com subdomain SNIs. `googl.com` is a Google-owned redirect alias served off the same GFE pool, so the TLS handshake works against `google_ip:443` without extra plumbing; we just present the name in the ClientHello for fingerprint diversity. Mirrored into the Android default pool too. The mipsel-softfloat target finally builds green in CI — two earlier bugs that compounded: messense doesn't publish a `:mipsel-musl-softfloat` image tag (fixed in main earlier by using `mipsel-musl` + `RUSTFLAGS=-C target-feature=+soft-float` + `-Z build-std`), and the pre-installed nightly in that image has a broken component state that rustup can't upgrade in place (fixed by uninstalling nightly first). Both fixes are in the tagged commit this time. Closes issue #26. Previous issues addressed in v1.1.0 that this release documents the closing of: - issue #28: "egui_glow requires opengl 2.0+" on old Windows / RDP / VMs — fixed via dual glow+wgpu compile + MHRV_RENDERER env var + run.bat auto-retry. - issue #37: connection-mode picker (VPN/TUN vs Proxy-only) so users who already run another VPN can still use mhrv-rs as a per-app HTTP/SOCKS5 proxy. Version bump: 1.1.0 → 1.1.1 (versionCode 110 → 111). |
||
|
|
28be8f67d5 |
v1.1.0: unified Connect button, proxy mode, app splitting, Persian UI, MIPS build (#41)
Major feature release across Android + desktop. Six items the user asked for, verified end-to-end on the emulator. Android ------- * Unified Connect/Disconnect button. Single large button swaps between green "Connect" (when the service is down) and red "Disconnect" (when it's up). Tracks the real service state via a new process-wide `VpnState` singleton flipped from the service's startEverything() / teardown() — not optimistic, the button only reports what the service actually did. * Connection mode dropdown (issue #37). Two options: VPN (TUN) — routes every app — and Proxy only — user configures per-app via Wi-Fi proxy to 127.0.0.1:8080 (HTTP) / :1081 (SOCKS5). PROXY_ONLY skips VpnService.prepare() entirely (no OS VPN grant prompt) and the service just keeps the foreground listeners up. Default is VPN_TUN so existing behaviour is preserved for users who upgrade without looking at the dropdown. * App splitting. In VPN_TUN mode you can pick All / Only selected / All except selected, with a picker dialog that lists installed user-visible apps (LazyColumn with search, "show system apps" toggle, multi-select checkboxes). ONLY calls `Builder.addAllowedApplication()` for each chosen package; EXCEPT calls `addDisallowedApplication()` additive to the mandatory self-exclude. Requires QUERY_ALL_PACKAGES — added to the manifest along with a `<queries>` launcher-intent filter so the picker rows can render app labels, not just package strings. * Persian/English UI toggle with RTL. Top-bar TextButton cycles AUTO → FA → EN → AUTO. Persian strings live in `res/values-fa/strings.xml`; English in `res/values/strings.xml`. `AppCompatDelegate.setApplicationLocales()` is used as the persistence layer (plus `AppLocalesMetadataHolderService` meta and `locales_config.xml` for the per-app-language OS entry on API 33+). MainActivity overrides `attachBaseContext` to wrap the context with the right locale at the earliest possible moment — otherwise a saved preference wouldn't apply until the SECOND process after toggling. RTL swaps automatically because Persian is script="Arab" in Android's locale database. * Collapsible How-to-use card. The big instruction block that used to dominate the bottom of the screen now lives inside a CollapsibleSection that starts expanded for a fresh install (empty deployment URLs / auth_key) and collapsed otherwise. * Update check auto-fires on first composition, silent-on-up-to-date, snackbar-only-if-available. Still surfaces via the version badge tap for manual checks. * MhrvVpnService teardown guard was kept from v1.0.2 — `AtomicBoolean` makes the second caller a no-op, which is the SIGSEGV fix for "tap Stop, app closes" from before. Stress-tested under rapid Connect/Disconnect cycles. Desktop ------- * Fix: Advanced section silently resetting on every Save. `ConfigWire` was missing `fetch_ips_from_api` / `max_ips_to_scan` / `scan_batch_size` / `google_ip_validation` — every persist dropped them, every reload fell back to the serde defaults, user saw their Advanced toggles reset. Added the fields to the wire struct (issue surfaced by the user as "Advanced resets after reopening the app"). * Windows renderer fallback (issue #28). `eframe` is now built with BOTH `glow` (OpenGL 2+) and `wgpu` (DX12/Vulkan/Metal); runtime defaults to glow for compat but honours `MHRV_RENDERER=wgpu` for boxes that crash with "egui_glow requires opengl 2.0+" — old Windows hardware, RDP sessions, VMs without GPU acceleration. `run.bat` auto-retries the UI with `MHRV_RENDERER=wgpu` if the first launch exits non-zero, so users don't need to know about the flag. CI -- * Added OpenWRT mipsel-softfloat build target (issue #26). MT7621 routers specifically need soft-float because the CPU has no FPU; a hard-float binary segfaults on first fp op. Built via `messense/rust-musl-cross:mipsel-musl-softfloat` docker image + nightly Rust with `-Z build-std` (mipsel is Rust tier 3 since 1.72, no pre-built std). Marked `continue-on-error: true` — the tier-3 target occasionally regresses and we'd rather ship the rest of the release than block on MT7621 support. Signature / versioning ---------------------- * versionCode 110, versionName 1.1.0; Cargo bumped to 1.1.0. * Release APK signed with the committed `release.jks` (same as v1.0.2), so v1.0.2 → v1.1.0 upgrades install in-place without the uninstall-first dance. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
64409f6b41 |
v1.0.2: stable release signature, idempotent Stop, top-level Settings for CA install (#33)
Three fixes + one behaviour change from v1.0.1 reports. APK signature is now stable (release.jks committed) ---------------------------------------------------- v1.0.0 and v1.0.1 signed release APKs with Gradle's auto-generated debug keystore, which is randomly generated per machine and per CI runner. Result: every upgrade failed with INSTALL_FAILED_UPDATE_INCOMPATIBLE and users had to uninstall first. Unfixable without a stable key. android/app/release.jks now holds that key, committed to the repo with the password in plaintext in build.gradle.kts. This is fine for a FOSS sideload project without a Play Store identity — the trust model is "trust the source tree you pulled from," not "trust the key we hold." Anyone forking and shipping a rebranded build should generate their own key. One-time cost: v1.0.1 → v1.0.2 STILL requires uninstall, because we're switching signature keys. Every upgrade from v1.0.2 onward is clean. Stop no longer (sometimes) closes the app ----------------------------------------- teardown() is reachable from three paths on two threads: 1. ACTION_STOP onStartCommand branch (mhrv-teardown worker) 2. onDestroy after stopSelf (main thread) 3. VpnService revocation out-of-band (main thread) Running the full native cleanup sequence twice races the two threads through Tun2proxy.stop() → fd.close() → Native.stopProxy(handle) on state that's already been nullified — SIGSEGV source, user-visible as "tap Stop, app disappears." New AtomicBoolean `tornDown` gates entry: first caller wins, every subsequent caller logs "teardown: already done" and returns. onDestroy also wraps the call in try/catch — crashing out of onDestroy takes the whole process with it, which is exactly the bug we're trying to fix. Smoke-tested on emulator: teardown now logs teardown: begin caller=mhrv-teardown ... clean sequence ... teardown: done onDestroy entered teardown: already done, skipping (caller=main) onDestroy done with PID unchanged throughout. CA install now routes to the Settings search -------------------------------------------- Old flow: `Settings.ACTION_SECURITY_SETTINGS` deep-link, then walk "Encryption & credentials → Install a certificate → CA certificate". That path varies wildly between OEMs (Samsung buries it under "Biometrics and security → Other security settings"; Xiaomi under "Passwords & Security → Privacy"; Pixel splits it between "More security settings" and "Privacy controls" depending on Android version). Users got lost. New flow: open the top-level Settings app (`Settings.ACTION_SETTINGS`) and instruct the user to use the Settings search bar to find "CA certificate". Search is consistent across OEMs and Android versions; the menu paths are not. Dialog, snackbar, and `docs/android.md` copy all updated to match. Version bump: 1.0.1 → 1.0.2 (versionCode 101 → 102). releases/mhrv-rs-android-universal-v1.0.1.apk replaced with the v1.0.2 build. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b734f41faa |
v1.0.1: auto-resolve google_ip, robust Stop, Check-for-updates, front_domain repair (#31)
Three reported issues from v1.0.0 — one real bug, two UX gaps. google_ip auto-resolve (THE FIX) -------------------------------- Google rotates the A record for www.google.com across their anycast pool. A hardcoded default IP breaks new installs on any network that isn't geo-homed to the same edge — symptom is "all SNIs time out" even with a fresh deployment. On Start and via a new "Auto-detect" button, we now do a JVM-side InetAddress lookup BEFORE establishing the VPN (so the resolver uses the underlying network, not our own Virtual-DNS TUN — avoids a loop), update the config, and continue. The auto-resolve lives in the HomeScreen click handler (not MainActivity) so it goes through the same `persist(cfg)` the text fields use. Previous iteration did `ConfigStore.load → modify → save` directly to disk, which left Compose's in-memory cfg stale and a subsequent field edit would overwrite the fresh IP. One source of truth now. Also defensively repairs front_domain: if it's been corrupted into an IP literal (bad paste, whatever) we restore "www.google.com" — the TLS SNI on the outbound leg has to be a hostname or the handshake lands on the wrong vhost. Robust Stop ----------- The Stop button now dispatches both ACTION_STOP (graceful: runs teardown, stops tun2proxy, closes TUN fd, shuts down Rust runtime) AND stopService() (defensive: covers force-closed-then-reopened zombie state where Android auto-restarted our START_STICKY service in a fresh process and the in-memory TUN reference is gone). Check-for-updates ----------------- Tapping the version badge in the top bar now runs the same update_check that the desktop UI uses, via a new `Native.checkUpdate()` JNI entry point. Returns a JSON blob the Kotlin side parses into an "Up to date", "Update available: v→v <url>", "Offline: ...", or "Check failed: ..." snackbar. Mirrors the desktop's behavior so a user doesn't have to manually poll GitHub for new builds. Crash visibility ---------------- New MhrvApp.kt registers a process-wide uncaught exception handler. Crashes are now stamped into logcat under the `mhrv-crash` tag with the thread name before the default handler kills the process — previously the JVM crash in coroutines / the log drain / the tun2proxy worker was invisible unless you caught the dropoff in real time. Version bump: 1.0.0 → 1.0.1 (versionCode 100 → 101). Release APK rebuilt and replaces the 1.0.0 copy in releases/; CI will regenerate on the v1.0.1 tag push. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
96d1352728 |
Add Android app with full TUN bridge + two proxy fixes the desktop also wants (#29)
The app is a Kotlin/Compose front-end that reuses the mhrv-rs crate
via JNI. It speaks VpnService to get a TUN fd, hands that to tun2proxy,
and funnels every app's traffic through the in-process SOCKS5 listener —
no per-app proxy setup on the device.
Two fixes in `src/proxy_server.rs` apply to desktop builds too:
* SNI peek via `LazyConfigAcceptor`. When a browser uses DoH (Chrome's
default), tun2proxy hands us a raw IP in the SOCKS5 CONNECT. Minting
a MITM cert for the IP produced `ERR_CERT_COMMON_NAME_INVALID` on
Cloudflare-fronted sites. We now read the ClientHello's SNI first
and use that both as the cert subject and as the upstream host for
the Apps Script relay (fetching `https://<IP>/...` with an IP in the
Host header gets rejected by CF anyway).
* Short-circuit CORS preflight at the MITM boundary. `UrlFetchApp.fetch()`
rejects `OPTIONS` with a Swedish "Ett attribut med ogiltigt värde
har angetts: method" error, which silently broke every fetch()/XHR
preflight and was the root cause of "JS doesn't load" on Discord,
Yahoo, and similar. Since we already terminate the TLS the browser
talks to, answering the preflight with a permissive 204 is safe —
the real request still goes through the relay.
Android-side capabilities (feature-parity with `mhrv-rs-ui` where it
fits on a phone):
* multi-deployment ID editor
* SNI rotation pool + per-SNI "Test" + "Test all" (JNI into scan_sni)
* live logs panel (JNI ring buffer drained on a 500 ms poll)
* Advanced section: verify_ssl, parallel_relay, log_level, upstream_socks5
* CA install flow that matches modern Android's reality: saves
`Downloads/mhrv-ca.crt` via MediaStore, deep-links Security settings,
then verifies post-hoc by fingerprint lookup in AndroidCAStore (the
KeyChain intent dead-ends with a Close-only dialog on Android 11+)
* Start/Stop debounced to dodge an emulator EGL renderer crash on
rapid taps
Theme matches the desktop palette exactly — always-dark, accent
`#4678B4`, card fill `#1C1E22`, 4dp button / 6dp card radii.
No dynamic color, no light scheme: the desktop is always dark and
we follow.
Build wiring:
* `Cargo.toml`: `cdylib` crate-type added; `jni` + `tun2proxy`
scoped to `cfg(target_os = "android")` so desktop builds pay
nothing.
* `src/data_dir.rs`: `set_data_dir()` override so the Android app's
private filesDir replaces the `directories` crate's desktop default.
* `src/android_jni.rs`: JNI entry points for start/stop/exportCa plus
a ring buffer draining to `Native.drainLogs()` and `testSni()` that
wraps `scan_sni::probe_one`.
* Gradle task chain runs `cargo ndk` before each assemble; post-step
normalizes tun2proxy's hash-suffixed cdylib to a stable filename
so `System.loadLibrary("tun2proxy")` works.
Verified end-to-end on an API 34 emulator: ipleak, yahoo, discord,
cloudflare.com all render; TLS is MITM-ed under our user-installed
CA; service survives rapid Stop/Start cycles.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|