mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-18 05:44:35 +03:00
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.
This commit is contained in:
Generated
+1
-1
@@ -2186,7 +2186,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mhrv-rs"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mhrv-rs"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
edition = "2021"
|
||||
description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting"
|
||||
license = "MIT"
|
||||
|
||||
@@ -14,8 +14,8 @@ android {
|
||||
applicationId = "com.therealaleph.mhrv"
|
||||
minSdk = 24 // Android 7.0 — covers 99%+ of live devices.
|
||||
targetSdk = 34
|
||||
versionCode = 121
|
||||
versionName = "1.2.1"
|
||||
versionCode = 122
|
||||
versionName = "1.2.2"
|
||||
|
||||
// Ship all four mainstream Android ABIs:
|
||||
// - arm64-v8a — 95%+ of real-world Android phones since 2019
|
||||
|
||||
@@ -299,4 +299,6 @@ val DEFAULT_SNI_POOL: List<String> = listOf(
|
||||
"translate.google.com",
|
||||
"play.google.com",
|
||||
"lens.google.com",
|
||||
// Issue #75.
|
||||
"chromewebstore.google.com",
|
||||
)
|
||||
|
||||
@@ -83,8 +83,30 @@ class MhrvVpnService : VpnService() {
|
||||
Native.setDataDir(filesDir.absolutePath)
|
||||
|
||||
val cfg = ConfigStore.load(this)
|
||||
if (!cfg.hasDeploymentId || cfg.authKey.isBlank()) {
|
||||
Log.e(TAG, "Config is incomplete — can't start proxy")
|
||||
|
||||
// Android 8+ requires every service started via
|
||||
// `startForegroundService()` to call `startForeground()` within a
|
||||
// short window or the system crashes the app with
|
||||
// `ForegroundServiceDidNotStartInTimeException`. Every `stopSelf()`
|
||||
// path below MUST therefore happen after a `startForeground()`
|
||||
// call — otherwise the user-visible symptom is "the app crashes
|
||||
// the instant I tap Start". See issue #73: user configured
|
||||
// google_only mode (no deployment ID needed), which tripped the
|
||||
// old early-return-before-startForeground branch.
|
||||
//
|
||||
// We call startForeground immediately here with the notification
|
||||
// used by the normal running state; if we bail out below, we
|
||||
// tear the foreground service down in an orderly way.
|
||||
startForeground(NOTIF_ID, buildNotif(cfg.listenPort))
|
||||
|
||||
// Deployment ID + auth key are only required in apps_script mode.
|
||||
// google_only mode (bootstrap / Telegram-only use cases) runs
|
||||
// with neither. Closes #73 regression where google_only users
|
||||
// hit this branch and crashed on startForeground timeout.
|
||||
val needsAppsScriptCreds = cfg.mode == Mode.APPS_SCRIPT
|
||||
if (needsAppsScriptCreds && (!cfg.hasDeploymentId || cfg.authKey.isBlank())) {
|
||||
Log.e(TAG, "Config is incomplete — can't start proxy in apps_script mode")
|
||||
try { stopForeground(STOP_FOREGROUND_REMOVE) } catch (_: Throwable) {}
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
@@ -104,6 +126,7 @@ class MhrvVpnService : VpnService() {
|
||||
proxyHandle = Native.startProxy(cfg.toJson())
|
||||
if (proxyHandle == 0L) {
|
||||
Log.e(TAG, "Native.startProxy returned 0 — see logcat tag mhrv_rs")
|
||||
try { stopForeground(STOP_FOREGROUND_REMOVE) } catch (_: Throwable) {}
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
@@ -115,12 +138,11 @@ class MhrvVpnService : VpnService() {
|
||||
// another VPN app already owns the system VPN slot, the user
|
||||
// wants per-app opt-in via Wi-Fi proxy settings, or the device
|
||||
// is a sandboxed/rooted setup where VpnService is unwelcome.
|
||||
// We still run as a foreground service (required for the native
|
||||
// listener thread to survive backgrounding), we just skip every
|
||||
// VPN-specific step below. Issue #37.
|
||||
// We already called startForeground() at the top of this method,
|
||||
// which is all PROXY_ONLY needs for the listener thread to survive
|
||||
// backgrounding. Issue #37.
|
||||
if (cfg.connectionMode == ConnectionMode.PROXY_ONLY) {
|
||||
Log.i(TAG, "PROXY_ONLY mode: listeners up, skipping VpnService/TUN")
|
||||
startForeground(NOTIF_ID, buildNotif(cfg.listenPort))
|
||||
VpnState.setRunning(true)
|
||||
return
|
||||
}
|
||||
@@ -202,6 +224,7 @@ class MhrvVpnService : VpnService() {
|
||||
Log.e(TAG, "establish() returned null — is VPN permission granted?")
|
||||
Native.stopProxy(proxyHandle)
|
||||
proxyHandle = 0L
|
||||
try { stopForeground(STOP_FOREGROUND_REMOVE) } catch (_: Throwable) {}
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
@@ -232,7 +255,10 @@ class MhrvVpnService : VpnService() {
|
||||
}
|
||||
}, "tun2proxy").apply { start() }
|
||||
|
||||
startForeground(NOTIF_ID, buildNotif(cfg.listenPort))
|
||||
// (startForeground was already called at the top of this method
|
||||
// to satisfy Android 8+'s foreground-service contract — see the
|
||||
// comment at the start of startEverything. Calling it here again
|
||||
// would be a no-op but wasteful.)
|
||||
|
||||
// Publish "running" state for the UI's Connect/Disconnect button
|
||||
// to observe. Only flipped true once everything above succeeded —
|
||||
|
||||
@@ -385,12 +385,26 @@ fun HomeScreen(
|
||||
// so a subsequent field edit can't overwrite the
|
||||
// fresh values with pre-resolve ones.
|
||||
scope.launch {
|
||||
val fresh = withContext(Dispatchers.IO) {
|
||||
NetworkDetect.resolveGoogleIp()
|
||||
}
|
||||
// Only auto-fill google_ip if it's empty.
|
||||
// Issue #71: some Iranian ISPs return
|
||||
// poisoned A records for www.google.com that
|
||||
// resolve but then refuse TLS (or route to a
|
||||
// Google IP that's not on the GFE and can't
|
||||
// handle our SNI-rewrite). If the user has
|
||||
// manually set a working IP
|
||||
// (e.g. 216.239.38.120), we must NOT
|
||||
// overwrite it with a poisoned fresh lookup
|
||||
// just because the two values differ. They
|
||||
// can still force a re-resolve via the
|
||||
// explicit "Auto-detect" button above.
|
||||
var updated = cfg
|
||||
if (!fresh.isNullOrBlank() && fresh != updated.googleIp) {
|
||||
updated = updated.copy(googleIp = fresh)
|
||||
if (updated.googleIp.isBlank()) {
|
||||
val fresh = withContext(Dispatchers.IO) {
|
||||
NetworkDetect.resolveGoogleIp()
|
||||
}
|
||||
if (!fresh.isNullOrBlank()) {
|
||||
updated = updated.copy(googleIp = fresh)
|
||||
}
|
||||
}
|
||||
if (updated.frontDomain.isBlank() ||
|
||||
updated.frontDomain.parseAsIpOrNull() != null
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<!-- see docs/changelog/v1.1.0.md for the file format: Persian, then `---`, then English. -->
|
||||
• رفع کرش اندروید هنگام Start وقتی کاربر حالت google_only را با فیلدهای خالی deployment استفاده میکرد: همهٔ مسیرهای early-return اکنون قبل از stopSelf تابع startForeground را صدا میکنند تا قرارداد foreground-service اندروید ۸+ شکسته نشود (issue #73)
|
||||
• رفع جایگزینشدن خودکار google_ip: حالا فقط وقتی فیلد خالی است، DNS lookup انجام میشود. اگر دستی یک IP کارا تنظیم کردهاید، دیگر با Start روی آن نوشته نمیشود (issue #71)
|
||||
• افزودن chromewebstore.google.com به پول SNI (issue #75)
|
||||
• رفع طول Content-Length در پاسخ ۵۰۲ حالت google_only برای HTTP ساده (PR #70)
|
||||
---
|
||||
• Fix Android crash on Start when the user picks google_only mode with empty deployment fields: every early-return path now calls startForeground before stopSelf so we don't violate Android 8+'s foreground-service contract (issue #73)
|
||||
• Fix google_ip auto-overwrite: DNS lookup only fires when the field is blank. If you manually set a working IP, Start no longer clobbers it on every launch (issue #71)
|
||||
• Add chromewebstore.google.com to the SNI pool (issue #75)
|
||||
• Fix Content-Length in the google_only plain-HTTP 502 response (PR #70)
|
||||
@@ -1106,6 +1106,10 @@ pub const DEFAULT_GOOGLE_SNI_POOL: &[&str] = &[
|
||||
"translate.google.com",
|
||||
"play.google.com",
|
||||
"lens.google.com",
|
||||
// chromewebstore.google.com — reported in issue #75 as a working
|
||||
// SNI. Same family as the rest: wildcard cert, GFE-hosted,
|
||||
// handshake against google_ip:443 with no content negotiation.
|
||||
"chromewebstore.google.com",
|
||||
];
|
||||
|
||||
/// Build the pool of SNI hosts used for outbound connections to the Google
|
||||
|
||||
Reference in New Issue
Block a user