Skip to content

Commit ea9724c

Browse files
fix(android): app splitting ONLY mode — don't mix allowed/disallowed apps
1 parent 8e5be57 commit ea9724c

1 file changed

Lines changed: 26 additions & 20 deletions

File tree

android/app/src/main/java/com/therealaleph/mhrv/MhrvVpnService.kt

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -160,45 +160,51 @@ class MhrvVpnService : VpnService() {
160160
.addRoute("0.0.0.0", 0)
161161
.addDnsServer("1.1.1.1")
162162
.setBlocking(false)
163-
try {
164-
builder.addDisallowedApplication(packageName)
165-
} catch (e: Throwable) {
166-
// Shouldn't happen for our own package, but don't hard-fail.
167-
Log.w(TAG, "addDisallowedApplication failed: ${e.message}")
168-
}
169163

170-
// Apply user-chosen app splitting on top of the mandatory
171-
// self-exclusion above.
164+
// Apply user-chosen app splitting. The VpnService API treats
165+
// addAllowedApplication and addDisallowedApplication as mutually
166+
// exclusive — calling both on one Builder throws
167+
// IllegalArgumentException at establish() time, which is the bug
168+
// that manifested as "ONLY mode tunnels everything" (establish()
169+
// failed silently and the fallback never routed correctly).
172170
//
173-
// ALL — no extra restriction; every other app routes through
174-
// us. Matches pre-splitting behaviour.
175-
// ONLY — allow-list. addAllowedApplication() for each chosen
176-
// package; anything missing from the list bypasses the
177-
// VPN on the OS-native route. Note that ONLY and the
178-
// mandatory self-exclude are mutually exclusive in the
179-
// VpnService API, so if the user also put us in the
180-
// allow-list we skip the self-exclude (it's already
181-
// implicit via "we're not in the list").
182-
// EXCEPT — deny-list. addDisallowedApplication() for each chosen
183-
// package, additive with our self-exclude.
171+
// ALL / EXCEPT: add the mandatory self-exclude (packageName) via
172+
// addDisallowedApplication so our own proxy's outbound to
173+
// google_ip doesn't loop through the TUN.
174+
// ONLY: self-exclusion is implicit — we're not in the allow-list.
184175
//
185176
// Packages that are not installed (leftover selections from a
186177
// previous device) throw PackageManager.NameNotFoundException —
187178
// we log and skip rather than aborting the whole VPN start.
188179
when (cfg.splitMode) {
189-
SplitMode.ALL -> { /* no-op */ }
180+
SplitMode.ALL -> {
181+
try {
182+
builder.addDisallowedApplication(packageName)
183+
} catch (e: Throwable) {
184+
Log.w(TAG, "addDisallowedApplication(self) failed: ${e.message}")
185+
}
186+
}
190187
SplitMode.ONLY -> {
191188
if (cfg.splitApps.isEmpty()) {
192189
Log.w(TAG, "ONLY mode with empty splitApps list — no app would get the VPN; falling back to ALL")
190+
try {
191+
builder.addDisallowedApplication(packageName)
192+
} catch (_: Throwable) {}
193193
} else {
194194
for (pkg in cfg.splitApps) {
195+
if (pkg == packageName) continue // can't tunnel ourselves
195196
try { builder.addAllowedApplication(pkg) } catch (e: Throwable) {
196197
Log.w(TAG, "addAllowedApplication($pkg) failed: ${e.message}")
197198
}
198199
}
199200
}
200201
}
201202
SplitMode.EXCEPT -> {
203+
try {
204+
builder.addDisallowedApplication(packageName)
205+
} catch (e: Throwable) {
206+
Log.w(TAG, "addDisallowedApplication(self) failed: ${e.message}")
207+
}
202208
for (pkg in cfg.splitApps) {
203209
if (pkg == packageName) continue // already self-excluded above
204210
try { builder.addDisallowedApplication(pkg) } catch (e: Throwable) {

0 commit comments

Comments
 (0)