diff --git a/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/vpn/RebreakVpnService.kt b/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/vpn/RebreakVpnService.kt index 61d237c..659435b 100644 --- a/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/vpn/RebreakVpnService.kt +++ b/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/vpn/RebreakVpnService.kt @@ -38,6 +38,7 @@ class RebreakVpnService : VpnService() { private var tun: ParcelFileDescriptor? = null private var workerThread: Thread? = null @Volatile private var running = false + @Volatile private var selfHealActive = false private lateinit var hashList: HashList override fun onCreate() { @@ -61,6 +62,13 @@ class RebreakVpnService : VpnService() { startForeground(NOTIF_ID, buildNotification()) hashList.load() 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() 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() { Log.i(TAG, "onRevoke: User hat VPN in System-Settings deaktiviert") clearEnabledFlag()