fix(vpn): bypass own domains in DNS filter (rebreak.org, rebreak.app)

OAuth-Callbacks gehen an db-staging.rebreak.org — wenn der In-Flight-Cap
kurz erreicht wird, kriegt das SERVFAIL statt einer echten Antwort.
Eigene Infrastruktur-Domains explizit als Bypass deklariert: werden nie
aus der Blocklist geblockt und umgehen den In-Flight-Zähler nicht
(Forward läuft weiterhin normal, aber Block-Entscheidung wird übersprungen).

Gilt für iOS (PacketTunnelProvider) und Android (DnsFilter) gleichzeitig.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-06-01 05:12:50 +02:00
parent b1d382bada
commit 617312f367
2 changed files with 23 additions and 4 deletions

View File

@ -24,6 +24,7 @@ object DnsFilter {
private const val UPSTREAM_DNS = "1.1.1.1"
private const val UPSTREAM_PORT = 53
private const val DNS_PORT = 53
private val BYPASS_DOMAIN_SUFFIXES = listOf("rebreak.org", "rebreak.app")
private val forwardPool: ThreadPoolExecutor =
Executors.newCachedThreadPool() as ThreadPoolExecutor
@ -73,12 +74,17 @@ object DnsFilter {
val srcIp = packet.copyOfRange(12, 16)
val dstIp = packet.copyOfRange(16, 20)
if (hashList.matchesAnySuffix(domain)) {
// Eigene Infrastruktur-Domains nie blocken (OAuth-Callbacks, Backend-API).
val isBypass = BYPASS_DOMAIN_SUFFIXES.any { suffix ->
domain == suffix || domain.endsWith(".$suffix")
}
if (!isBypass && hashList.matchesAnySuffix(domain)) {
Log.i(TAG, "BLOCKED: $domain")
val resp = buildNxDomainResponse(packet, length, ihl, srcIp, dstIp, srcPort, dstPort)
writeSynchronized(output, outputLock, resp)
return
}
if (isBypass) Log.d(TAG, "BYPASS (own domain): $domain")
// Async forward — kopiere Buffer-Slice damit nicht überschrieben wird beim
// nächsten read() im VpnService-Loop

View File

@ -51,6 +51,12 @@ private let TUNNEL_SUBNET_MASK = "255.255.255.0"
private let UPSTREAM_DNS_HOST = "1.1.1.1"
private let UPSTREAM_DNS_PORT: UInt16 = 53
// Hardcodierter Bypass: eigene Infrastruktur-Domains niemals blocken,
// unabhängig von Blocklist-Hash-Kollisionen oder In-Flight-Cap.
// Betrifft Supabase-Auth-Callbacks (db-staging.rebreak.org),
// Backend-API (staging.rebreak.org) und zukünftige prod-Domains.
private let BYPASS_DOMAIN_SUFFIXES = ["rebreak.org", "rebreak.app"]
// Extension-Log-Store
/// Schreibt in den geteilten App-Group-Log-Store (`url_filter_logs`), den die
@ -273,9 +279,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
switch DnsFilter.classify(packet: packet, hashList: hashList) {
case .block(let response, let domain):
ExtLog.write("BLOCKED: \(domain)")
// Synthetische NXDOMAIN-Response sofort zurück ins TUN.
writeToTun(response)
// Eigene Infrastruktur-Domains nie blocken auch wenn Hash-Kollision.
// Betrifft OAuth-Callbacks (db-staging.rebreak.org) und Backend-API.
let isBypass = BYPASS_DOMAIN_SUFFIXES.contains { domain == $0 || domain.hasSuffix(".\($0)") }
if isBypass {
ExtLog.write("BYPASS (own domain): \(domain)")
forwardPacket(packet)
} else {
ExtLog.write("BLOCKED: \(domain)")
writeToTun(response)
}
case .forward:
forwardPacket(packet)