fix(ios-vpn): PacketTunnel Self-Heal — Blocklist-Retry bei leerem Start

startTunnel lädt blocklist.bin manchmal mit 0 Hashes (Datei wegen
Data-Protection bei gesperrtem Gerät noch nicht lesbar). Bisher blieb
Layer 1 dann tot bis zum nächsten App-Sync — in den Geräte-Logs als
~30-Min-Fenster mit 0 Hashes sichtbar (z.B. 16:22 startTunnel→0,
erst 16:55 per Darwin-Reload geheilt).

scheduleBlocklistRetryIfEmpty: lädt nach 3/10/30/60/120/300s erneut,
bis Hashes da sind (max 20 Versuche). Sobald das Gerät entsperrt ist,
wird die Datei lesbar → Self-Heal greift, ohne auf einen App-Sync zu
warten.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-05-22 19:07:31 +02:00
parent 38a74dd1ad
commit 23b91a1a3e

View File

@ -189,6 +189,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
ExtLog.write("✅ TUN-Settings gesetzt — Read-Loop startet")
self.running = true
self.readPackets()
// Self-Heal: war die Blocklist beim Start leer (Data-Protection bei
// gesperrtem Gerät), per Backoff erneut laden bis Hashes da sind.
self.scheduleBlocklistRetryIfEmpty(attempt: 0)
completionHandler(nil)
}
}
@ -448,4 +451,37 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
hashList?.load()
ExtLog.write("blocklist reloaded — \(hashList?.count() ?? 0) Hashes")
}
/// Self-Heal: wenn `startTunnel` die Blocklist leer geladen hat (Datei wegen
/// iOS-Data-Protection noch nicht lesbar Tunnel-Start vor dem ersten
/// Entsperren seit Boot, oder eine alte mit `.complete` geschriebene Datei),
/// wird `load()` mit Backoff erneut versucht, bis Hashes da sind.
///
/// Ohne das bliebe Layer 1 still tot, bis die App das nächste Mal synct und
/// die Darwin-Notification feuert beobachtet in den Geräte-Logs als
/// ~30-Minuten-Fenster mit 0 Hashes. Der Retry läuft auf `forwardQueue`.
private func scheduleBlocklistRetryIfEmpty(attempt: Int) {
guard running else { return }
guard let list = hashList, list.count() == 0 else { return } // schon ok
let maxAttempts = 20
guard attempt < maxAttempts else {
ExtLog.write("⚠️ Blocklist nach \(maxAttempts) Retries leer — warte auf App-Sync")
return
}
// Backoff in Sekunden; ab Index 5 konstant 300 s.
let backoff: [Double] = [3, 10, 30, 60, 120]
let delay = attempt < backoff.count ? backoff[attempt] : 300
forwardQueue.asyncAfter(deadline: .now() + delay) { [weak self] in
guard let self = self, self.running else { return }
self.hashList?.load()
let n = self.hashList?.count() ?? 0
if n > 0 {
ExtLog.write("✅ blocklist self-heal — \(n) Hashes (Versuch \(attempt + 1))")
} else {
self.scheduleBlocklistRetryIfEmpty(attempt: attempt + 1)
}
}
}
}