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>
60 lines
1.8 KiB
Swift
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
|
|
}
|
|
}
|