fix(android-vpn): Blocklist-Self-Heal — kein Neustart nach erster Aktivierung

Der VpnService lädt die Blockliste bei onStartCommand(START). Ist
blocklist.bin beim ersten Aktivieren noch nicht gesynct → 0 Hashes.
syncBlocklist schickt zwar ACTION_RELOAD, aber via ctx.startService(),
das Android 8+ als Background-Start still verwerfen kann → Filter bleibt
auf 0 Hashes bis Geräte-Neustart: VPN aktiv, aber nichts geblockt.

scheduleBlocklistSelfHeal: lädt hashList nach 2/5/15/30/60s erneut bis
Hashes da sind (max 30 Versuche). Greift unabhängig vom ACTION_RELOAD-
Intent. Analog zum iOS-PacketTunnel-Self-Heal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-05-22 22:08:24 +02:00
parent 435aaeefb1
commit 9a22bcd114

View File

@ -38,6 +38,7 @@ class RebreakVpnService : VpnService() {
private var tun: ParcelFileDescriptor? = null private var tun: ParcelFileDescriptor? = null
private var workerThread: Thread? = null private var workerThread: Thread? = null
@Volatile private var running = false @Volatile private var running = false
@Volatile private var selfHealActive = false
private lateinit var hashList: HashList private lateinit var hashList: HashList
override fun onCreate() { override fun onCreate() {
@ -61,6 +62,13 @@ class RebreakVpnService : VpnService() {
startForeground(NOTIF_ID, buildNotification()) startForeground(NOTIF_ID, buildNotification())
hashList.load() hashList.load()
Log.i(TAG, "blocklist loaded — ${hashList.count()} hashes") Log.i(TAG, "blocklist loaded — ${hashList.count()} hashes")
// Self-Heal: war die Blockliste beim Start leer (blocklist.bin
// noch nicht gesynct, oder ACTION_RELOAD verschluckt), per
// Backoff erneut laden bis Hashes da sind.
if (hashList.count() == 0) {
Log.i(TAG, "blocklist beim Start leer — Self-Heal-Retry gestartet")
scheduleBlocklistSelfHeal()
}
startVpn() startVpn()
return START_STICKY return START_STICKY
} }
@ -145,6 +153,43 @@ class RebreakVpnService : VpnService() {
} }
} }
/**
* Self-Heal: hat der Service die Blockliste leer geladen `blocklist.bin`
* war beim Start noch nicht gesynct, ODER das `ACTION_RELOAD` nach
* `syncBlocklist` wurde von Androids Background-Start-Restriction
* verschluckt wird `hashList.load()` mit Backoff erneut versucht, bis
* Hashes da sind. Ohne das bliebe der Filter auf 0 Hashes bis zum nächsten
* Service-(=Geräte-)Neustart VPN aktiv, aber nichts wird geblockt.
*/
private fun scheduleBlocklistSelfHeal() {
if (selfHealActive) return
selfHealActive = true
Thread {
// Backoff in ms; ab Index 5 konstant 60 s. Max 30 Versuche (~25 min).
val backoff = longArrayOf(2_000, 5_000, 15_000, 30_000, 60_000)
var attempt = 0
try {
while (running && !Thread.currentThread().isInterrupted && attempt < 30) {
val delay = if (attempt < backoff.size) backoff[attempt] else 60_000L
Thread.sleep(delay)
if (!running) return@Thread
hashList.load()
val n = hashList.count()
if (n > 0) {
Log.i(TAG, "blocklist self-heal ok — $n hashes (Versuch ${attempt + 1})")
return@Thread
}
attempt++
}
Log.w(TAG, "blocklist self-heal: nach $attempt Versuchen weiter leer")
} catch (_: InterruptedException) {
// Service gestoppt — ok.
} finally {
selfHealActive = false
}
}.also { it.isDaemon = true; it.start() }
}
override fun onRevoke() { override fun onRevoke() {
Log.i(TAG, "onRevoke: User hat VPN in System-Settings deaktiviert") Log.i(TAG, "onRevoke: User hat VPN in System-Settings deaktiviert")
clearEnabledFlag() clearEnabledFlag()