chahinebrini 5a16cf771b feat(ios-protection): v1 NEPacketTunnelProvider DNS-Sinkhole als Layer-1
Neuer iOS-Layer-1-Filter: ein NEPacketTunnelProvider-DNS-Sinkhole — MDM-frei,
ab iOS 16, Parität zum Android-VPN-DNS-Filter. Ersetzt den Apple-seitig
blockierten NEURLFilter als Default. NEURLFilter-/PIR-Code bleibt inaktiv als
iOS-26-Upgrade-Pfad erhalten (User-Entscheidung).

Neues Extension-Target RebreakPacketTunnelExtension/:
- PacketTunnelProvider.swift — TUN-Setup (virtuelle DNS-IP 10.0.0.1, nur diese
  Route ins TUN), Read-Loop, NXDOMAIN-Sinkhole, Upstream-Forward via
  NWConnection zu 1.1.1.1, Blocklist-Reload via Darwin-Notification.
- DnsFilter.swift / HashList.swift / DomainHasher.swift — Swift-Ports der
  Android-DNS-Filter-Logik. blocklist.bin-Format (sortierte big-endian UInt64,
  SHA-256-Prefix) 1:1 beibehalten, mmap statt Heap-Load.

RebreakProtectionModule.swift:
- activateUrlFilter startet jetzt den Packet-Tunnel via NETunnelProviderManager
  (Default-Layer-1, On-Demand-Auto-Reconnect aktiv).
- NEURLFilter-Code in activateNeUrlFilter ausgelagert (inaktiv, behalten).
- getDeviceState/disable lesen bzw. stoppen den Tunnel-Status.

with-rebreak-protection-ios.js: zweites app_extension-Target, klassischer
Embed-Pfad (dstSubfolderSpec 13), packet-tunnel-provider-Entitlement + App-Group.
app.config.ts: zweites appExtensions-Target.

NICHT auf echtem Gerät verifiziert — NE-Packet-Tunnel laufen nicht im
Simulator. Ungetestete Annahmen im Code mit "UNGETESTETE ANNAHME" markiert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 23:13:54 +02:00

60 lines
1.8 KiB
Swift

/*
DomainHasher Swift-Port von
`android/.../filter/DomainHasher.kt`.
Domain-Hashing IDENTISCH zu `server/utils/domainHash.ts` und der Android-
Implementierung. Produziert die ersten 8 Bytes des SHA-256 als big-endian
UInt64. Der Server liefert die Hash-Liste in dieser Form; iOS und Android
lesen sie 1:1.
Privacy: arbeitet rein lokal keine Klartext-Domain verlässt das Gerät.
*/
import Foundation
import CryptoKit
enum DomainHasher {
/// Normalisiert einen Hostname analog zu Server + Android.
/// - lowercase
/// - http(s):// strippen
/// - Pfad/Query/Anchor (alles ab erstem `/`) abschneiden
/// - leading "www." entfernen
///
/// Muss BIT-IDENTISCH zu `DomainHasher.kt#normalize` bleiben, sonst matchen
/// die Hashes nicht gegen die vom Server generierte `blocklist.bin`.
static func normalize(_ host: String) -> String {
var h = host.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
if h.hasPrefix("https://") {
h = String(h.dropFirst(8))
} else if h.hasPrefix("http://") {
h = String(h.dropFirst(7))
}
if let slash = h.firstIndex(of: "/") {
h = String(h[h.startIndex..<slash])
}
if h.hasPrefix("www.") {
h = String(h.dropFirst(4))
}
return h
}
/// SHA-256(normalize(host)).first(8) als big-endian UInt64.
///
/// Bit-Pattern identisch zu Kotlins `ByteBuffer.order(BIG_ENDIAN).long`
/// und zum Server. Die ersten 8 Bytes des Digest werden big-endian
/// interpretiert.
static func hash(_ host: String) -> UInt64 {
let normalized = normalize(host)
let digest = SHA256.hash(data: Data(normalized.utf8))
var result: UInt64 = 0
var i = 0
for byte in digest {
result = (result << 8) | UInt64(byte)
i += 1
if i == 8 { break }
}
return result
}
}