@@ -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