Skip to content

Commit b82fc2a

Browse files
committed
refactor: remove full-tunnel toggle, always enable full-tunnel mode
- Remove fullTunnelEnabled preference key, Flow, setter and snapshot from AppPreferences - Remove fullTunnel toggle UI from ProtectionSection settings - Remove fullTunnelEnabled StateFlow and setFullTunnelEnabled from SettingsViewModel - Remove fullTunnel parameter from establishVpn(), always add 0.0.0.0/0 route - Remove fullTunnelEnabled parameter from GoTunnelAdapter.start(), always use startFull - Preserve WireGuard path: engine.start() when wgConfigJson is present
1 parent 94baefa commit b82fc2a

6 files changed

Lines changed: 42 additions & 95 deletions

File tree

app/src/main/java/app/pwhs/blockads/data/datastore/AppPreferences.kt

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ class AppPreferences(private val context: Context) {
6060
private val KEY_WG_ACTIVE_PROFILE_ID = stringPreferencesKey("wg_active_profile_id")
6161
private val KEY_HTTPS_FILTERING_ENABLED = booleanPreferencesKey("https_filtering_enabled")
6262
private val KEY_FILTER_HTTP3 = booleanPreferencesKey("filter_http3")
63-
private val KEY_FULL_TUNNEL_ENABLED = booleanPreferencesKey("full_tunnel_enabled")
6463
private val KEY_SELECTED_BROWSERS = stringSetPreferencesKey("selected_browsers")
6564
private val KEY_NETWORK_SWITCH_DELAY_ENABLED = booleanPreferencesKey("network_switch_delay_enabled")
6665
private val KEY_NETWORK_SWITCH_DELAY_SEC = intPreferencesKey("network_switch_delay_sec")
@@ -708,25 +707,6 @@ class AppPreferences(private val context: Context) {
708707
return context.dataStore.data.first()[KEY_FILTER_HTTP3] ?: false
709708
}
710709

711-
// Full-tunnel mode. Off (default) = split-tunnel (DNS-only, legacy
712-
// engine). On = full-network capture via the direct-TUN engine
713-
// (StartFull): all apps' traffic terminates in the userspace stack
714-
// (DNS filter + per-app firewall + passthrough; HTTPS MITM layered on
715-
// top when HTTPS filtering is also enabled).
716-
val fullTunnelEnabled: Flow<Boolean> = context.dataStore.data.map { prefs ->
717-
prefs[KEY_FULL_TUNNEL_ENABLED] ?: false
718-
}
719-
720-
suspend fun setFullTunnelEnabled(enabled: Boolean) {
721-
context.dataStore.edit { prefs ->
722-
prefs[KEY_FULL_TUNNEL_ENABLED] = enabled
723-
}
724-
}
725-
726-
suspend fun getFullTunnelEnabledSnapshot(): Boolean {
727-
return context.dataStore.data.first()[KEY_FULL_TUNNEL_ENABLED] ?: false
728-
}
729-
730710
suspend fun setSelectedBrowsers(packages: Set<String>) {
731711
context.dataStore.edit { prefs ->
732712
prefs[KEY_SELECTED_BROWSERS] = packages

app/src/main/java/app/pwhs/blockads/service/AdBlockVpnService.kt

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -408,10 +408,8 @@ class AdBlockVpnService : VpnService() {
408408

409409
// Load HTTPS Filtering setting
410410
val httpsFilteringEnabled = appPrefs.getHttpsFilteringEnabledSnapshot()
411-
// Full-tunnel mode. HTTPS filtering implies full-tunnel (it must
412-
// capture browser traffic); full-tunnel can also be on by itself.
413-
val fullTunnelEnabled =
414-
appPrefs.getFullTunnelEnabledSnapshot() || httpsFilteringEnabled
411+
// Full-tunnel mode is always enabled: all traffic is routed
412+
// through the direct-TUN engine (StartFull).
415413

416414

417415

@@ -429,7 +427,7 @@ class AdBlockVpnService : VpnService() {
429427

430428
var vpnEstablished = false
431429
while (!vpnEstablished && retryManager.shouldRetry()) {
432-
vpnEstablished = establishVpn(whitelistedApps, fullTunnelEnabled)
430+
vpnEstablished = establishVpn(whitelistedApps)
433431

434432
if (!vpnEstablished && retryManager.shouldRetry()) {
435433
Timber
@@ -555,7 +553,6 @@ class AdBlockVpnService : VpnService() {
555553
selectedBrowsers = selectedBrowsers,
556554
certDir = certDir,
557555
filterHttp3 = filterHttp3,
558-
fullTunnelEnabled = fullTunnelEnabled,
559556
socketProtector = { fd ->
560557
try {
561558
protect(fd)
@@ -575,8 +572,7 @@ class AdBlockVpnService : VpnService() {
575572
}
576573

577574
private fun establishVpn(
578-
whitelistedApps: Set<String>,
579-
fullTunnel: Boolean
575+
whitelistedApps: Set<String>
580576
): Boolean {
581577
// First check if the system still grants us the VPN permission.
582578
if (VpnService.prepare(this) != null) {
@@ -660,7 +656,7 @@ class AdBlockVpnService : VpnService() {
660656
b
661657
} else {
662658
// Direct mode — DNS + (optional) HTTPS local asset host.
663-
Timber.d("Establishing VPN in direct mode (fullTunnel=$fullTunnel)")
659+
Timber.d("Establishing VPN in direct mode (fullTunnel=true)")
664660
val b = Builder()
665661
.setSession("BlockAds")
666662
.addAddress("10.0.0.2", 32)
@@ -690,25 +686,23 @@ class AdBlockVpnService : VpnService() {
690686
// narrow asset route until the stack-forwarding path is
691687
// proven with the diagnostics added in startTcpStackParallel.
692688

693-
if (fullTunnel) {
694-
// Full-network capture (IPv4). This feeds the DEDICATED
695-
// full-tunnel data path (GoTunnelAdapter → engine.startFull),
696-
// where gVisor reads the TUN directly — no DnsInterceptor/
697-
// packetPipe bridge, so it doesn't deadlock under browser
698-
// load like the legacy parallel-stack path did. The stack
699-
// MITMs browser TCP, answers DNS on :53, and passes
700-
// everything else through a socket-protected dialer.
701-
//
702-
// IPv6 is deliberately NOT routed (no `::/0`): the Go
703-
// process usually has no working v6 route on mobile, so
704-
// tunnelled v6 would blackhole. v6 egresses directly
705-
// (unfiltered); browsers fall back to v4 for filtered
706-
// sites. DoT(853) is closed fast in the stack handler to
707-
// force plaintext DNS on 53. Private/loopback dests are
708-
// short-circuited to a direct dial by the handler's
709-
// Gate 0, so LAN stays reachable without an exclude route.
710-
b.addRoute("0.0.0.0", 0)
711-
}
689+
// Full-network capture (IPv4) — always enabled. This feeds
690+
// the DEDICATED full-tunnel data path (GoTunnelAdapter →
691+
// engine.startFull), where gVisor reads the TUN directly — no
692+
// DnsInterceptor/packetPipe bridge, so it doesn't deadlock
693+
// under browser load like the legacy parallel-stack path did.
694+
// The stack MITMs browser TCP, answers DNS on :53, and passes
695+
// everything else through a socket-protected dialer.
696+
//
697+
// IPv6 is deliberately NOT routed (no `::/0`): the Go
698+
// process usually has no working v6 route on mobile, so
699+
// tunnelled v6 would blackhole. v6 egresses directly
700+
// (unfiltered); browsers fall back to v4 for filtered
701+
// sites. DoT(853) is closed fast in the stack handler to
702+
// force plaintext DNS on 53. Private/loopback dests are
703+
// short-circuited to a direct dial by the handler's
704+
// Gate 0, so LAN stays reachable without an exclude route.
705+
b.addRoute("0.0.0.0", 0)
712706
b
713707
}
714708

app/src/main/java/app/pwhs/blockads/service/GoTunnelAdapter.kt

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ class GoTunnelAdapter(
274274
selectedBrowsers: Set<String> = emptySet(),
275275
certDir: String = "",
276276
filterHttp3: Boolean = false,
277-
fullTunnelEnabled: Boolean = false,
278277
socketProtector: ((Int) -> Boolean)? = null
279278
) {
280279
if (isRunning) return
@@ -341,24 +340,22 @@ class GoTunnelAdapter(
341340
socketProtector?.invoke(fd.toInt()) ?: false
342341
}
343342

344-
// Start the Go engine (this blocks the thread).
345-
//
346-
// Engine selection is by TUNNEL MODE, not by HTTPS:
347-
// • Full-tunnel (explicit toggle) OR HTTPS filtering → StartFull
348-
// (dedicated direct-TUN engine; gVisor reads the TUN directly,
349-
// no DnsInterceptor/packetPipe bridge → no under-load wedge).
350-
// HTTPS filtering merely adds the MITM layer inside it
351-
// (startStackMitm above); without it, full-tunnel still does
352-
// all-app DNS filter + per-app firewall + protected passthrough.
353-
// • Otherwise → legacy Start (split-tunnel DNS-only / WireGuard).
354-
val useFullTunnel = fullTunnelEnabled || (httpsFilteringEnabled && certDir.isNotEmpty())
355-
if (useFullTunnel) {
343+
// Engine selection:
344+
// • WireGuard → engine.start (WG handles its own full-route
345+
// tunneling; setup happens atomically inside Go before any
346+
// packets are read).
347+
// • Otherwise → engine.startFull (full-tunnel direct-TUN engine;
348+
// gVisor reads TUN directly — no DnsInterceptor/packetPipe
349+
// bridge, so it doesn't deadlock under browser load). HTTPS
350+
// MITM is layered on top when startStackMitm was called above;
351+
// without it, full-tunnel still does all-app DNS filter +
352+
// per-app firewall + protected passthrough.
353+
if (wgConfigJson.isNotEmpty()) {
354+
Timber.d("Starting Go tunnel engine in WIREGUARD mode (fd=$fd)")
355+
engine.start(fd.toLong(), protector, wgConfigJson)
356+
} else {
356357
Timber.d("Starting Go tunnel engine in FULL-TUNNEL mode (fd=$fd, mitm=${httpsFilteringEnabled && certDir.isNotEmpty()})")
357358
engine.startFull(fd.toLong(), protector)
358-
} else {
359-
// Legacy split-tunnel (DNS-only) / WireGuard path — unchanged.
360-
// WireGuard setup happens atomically inside Go before any packets are read.
361-
engine.start(fd.toLong(), protector, wgConfigJson)
362359
}
363360
}
364361

app/src/main/java/app/pwhs/blockads/ui/settings/SettingsScreen.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ fun SettingsScreen(
7272
val routingMode by viewModel.routingMode.collectAsStateWithLifecycle()
7373
val dnsResponseType by viewModel.dnsResponseType.collectAsStateWithLifecycle()
7474
val safeSearchEnabled by viewModel.safeSearchEnabled.collectAsStateWithLifecycle()
75-
val fullTunnelEnabled by viewModel.fullTunnelEnabled.collectAsStateWithLifecycle()
75+
7676
val youtubeRestrictedMode by viewModel.youtubeRestrictedMode.collectAsStateWithLifecycle()
7777
val dailySummaryEnabled by viewModel.dailySummaryEnabled.collectAsStateWithLifecycle()
7878
val milestoneNotificationsEnabled by viewModel.milestoneNotificationsEnabled.collectAsStateWithLifecycle()
@@ -122,11 +122,11 @@ fun SettingsScreen(
122122
networkSwitchDelaySec = networkSwitchDelaySec,
123123
safeSearchEnabled = safeSearchEnabled,
124124
youtubeRestrictedMode = youtubeRestrictedMode,
125-
fullTunnelEnabled = fullTunnelEnabled,
125+
126126
dnsResponseType = dnsResponseType,
127127
upstreamDNS = upstreamDNS,
128128
onSetAutoReconnect = { viewModel.setAutoReconnect(it) },
129-
onSetFullTunnelEnabled = { viewModel.setFullTunnelEnabled(it) },
129+
130130
onSetRoutingMode = { viewModel.setRoutingModeEnabled(it) },
131131
onSetNetworkSwitchDelayEnabled = { viewModel.setNetworkSwitchDelayEnabled(it) },
132132
onSetNetworkSwitchDelaySec = { viewModel.setNetworkSwitchDelaySec(it) },

app/src/main/java/app/pwhs/blockads/ui/settings/SettingsViewModel.kt

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,6 @@ class SettingsViewModel(
101101
val safeSearchEnabled: StateFlow<Boolean> = appPrefs.safeSearchEnabled
102102
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
103103

104-
val fullTunnelEnabled: StateFlow<Boolean> = appPrefs.fullTunnelEnabled
105-
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
106104

107105
val youtubeRestrictedMode: StateFlow<Boolean> = appPrefs.youtubeRestrictedMode
108106
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
@@ -257,17 +255,6 @@ class SettingsViewModel(
257255
}
258256
}
259257

260-
fun setFullTunnelEnabled(enabled: Boolean) {
261-
viewModelScope.launch {
262-
appPrefs.setFullTunnelEnabled(enabled)
263-
// Full-tunnel and WireGuard are mutually exclusive (the direct-TUN
264-
// engine dials destinations itself, bypassing the WG tunnel).
265-
if (enabled && appPrefs.getRoutingModeSnapshot() == AppPreferences.ROUTING_MODE_WIREGUARD) {
266-
appPrefs.setRoutingMode(AppPreferences.ROUTING_MODE_DIRECT)
267-
}
268-
requestVpnRestart()
269-
}
270-
}
271258

272259
fun setYoutubeRestrictedMode(enabled: Boolean) {
273260
viewModelScope.launch {

app/src/main/java/app/pwhs/blockads/ui/settings/component/ProtectionSection.kt

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ fun ProtectionSection(
4444
networkSwitchDelaySec: Int,
4545
safeSearchEnabled: Boolean,
4646
youtubeRestrictedMode: Boolean,
47-
fullTunnelEnabled: Boolean,
47+
4848
dnsResponseType: String,
4949
upstreamDNS: String,
5050
onSetAutoReconnect: (Boolean) -> Unit,
5151
onSetRoutingMode: (Boolean) -> Unit,
5252
onSetNetworkSwitchDelayEnabled: (Boolean) -> Unit,
5353
onSetNetworkSwitchDelaySec: (Int) -> Unit,
5454
onSetSafeSearchEnabled: (Boolean) -> Unit,
55-
onSetFullTunnelEnabled: (Boolean) -> Unit,
55+
5656
onSetYoutubeRestrictedMode: (Boolean) -> Unit,
5757
onShowDnsResponseTypeDialog: () -> Unit,
5858
onNavigateToDNSProvider: () -> Unit,
@@ -133,18 +133,7 @@ fun ProtectionSection(
133133
}
134134
}
135135
}
136-
// Full-tunnel mode
137-
HorizontalDivider(
138-
modifier = Modifier.padding(horizontal = 16.dp),
139-
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.1f)
140-
)
141-
SettingsToggleItem(
142-
icon = Icons.Default.Lan,
143-
title = stringResource(R.string.settings_full_tunnel),
144-
subtitle = stringResource(R.string.settings_full_tunnel_desc),
145-
isChecked = fullTunnelEnabled,
146-
onCheckedChange = onSetFullTunnelEnabled
147-
)
136+
148137
// Safe Search
149138
HorizontalDivider(
150139
modifier = Modifier.padding(horizontal = 16.dp),

0 commit comments

Comments
 (0)