Wie es funktioniert
Der Tunnel trägt nur DNS
Sift baut ein VpnService mit genau zwei Routen: 10.120.0.2/32 für IPv4 und, falls das Netz ein IPv6-Default-Route hat, fd00:120::2/128. Alles andere – Spielverkehr, Streaming, Downloads – läuft über die physische Verbindung.
addDnsServer() allein reicht dafür nicht: Android legt dabei keine Route an. Erst die explizite /32-Route bringt DNS in den Tunnel. Umgekehrt sorgt die fehlende 0.0.0.0/0-Route dafür, dass tun0 für allen anderen Verkehr gar nicht erst in Frage kommt.
Aus diesem Grund ist der Ping-Zuschlag null: Der Verkehr nimmt denselben Pfad wie ohne VPN.
Der Paketpfad
Der Kern ist reines C17 in libsift_core.so:
epoll_waitschläft, solange nichts passiert – kein Timer, kein Polling.- Ein
read()auf das TUN-Fd liefert ein Paket in einen statisch allokierten 2-KB-Puffer. - Der Parser prüft Version, Header-Länge, Protokoll (nur UDP), Zielport 53 und dass es eine Anfrage und keine Antwort ist.
- Der QNAME wird kleingeschrieben und labelweise umgedreht.
- Ein Lookup im Trie entscheidet
BLOCKoderALLOW. - Bei
BLOCKwird die Antwort im selben Puffer gebaut, inklusive Checksummen. BeiALLOWkommt der Cache oder der Upstream.
Es gibt keine Allokation pro Paket, keinen Thread pro Anfrage und kein memset über den Antwortpuffer.
Regeln: mmap statt Heap
Der Regelbestand liegt als komprimierter Reverse-Radix-Trie in einer Datei und wird read-only gemappt. Ein Knoten ist exakt 8 Byte groß; Labels liegen in einem String-Pool, Verweise sind uint32_t-Offsets, keine Zeiger.
Das ist der entscheidende Unterschied zu einer HashMap<String, …>: Die Seiten sind dateigestützt, vom Kernel reclaimbar und tauchen nicht als anonyme dirty memory auf. Ein Preset mit einigen hundert Domains belegt wenige Kilobyte; ein Import mit zehntausenden Einträgen bleibt im einstelligen Megabyte-Bereich – ohne dass der Java-Heap wächst.
Regeländerungen laufen über einen atomaren Austausch: kompilieren, fsync, rename, dann ein eventfd-Signal an den Engine-Thread. Der mappt die neue Datei, validiert Magic, Version und FNV-1a-Checksumme und tauscht den Zeiger. Ist die Datei kaputt, bleibt die alte Map aktiv.
Antworten richtig bauen
Geblockte A-Anfragen bekommen 0.0.0.0. Für AAAA gibt es NODATA statt :: – :: ist eine positive Antwort, und manche Clients versuchen trotzdem, dorthin zu verbinden. NODATA sagt sauber „kein IPv6 vorhanden“, und Happy Eyeballs fällt ohne Verzögerung auf IPv4 zurück.
Für HTTPS/SVCB gilt dasselbe: NODATA, damit Clients auf normales A/AAAA zurückfallen, statt an einem SVCB-Eintrag zu hängen.
CNAME-Uncloaking
Viele Tracker verstecken sich hinter einem unauffälligen Erstanbieter-Namen, der per CNAME auf ihr Netz zeigt. Sift läuft die CNAME-Kette einer Upstream-Antwort entlang – maximal vier Glieder, maximal 64 Records – und prüft jedes Ziel gegen den Trie. Trifft eines, wird die Antwort verworfen und der Client bekommt NXDOMAIN.
Cache
Ein LRU mit 256 Slots hält erlaubte Antworten. TTLs werden auf 30–3600 Sekunden geklemmt, damit kurze TTLs nicht jede Anfrage zum Upstream schicken und lange TTLs nicht ewig veralten. Zeitbasis ist CLOCK_BOOTTIME, damit ein 30-Sekunden-Eintrag einen zehnminütigen Systemschlaf überlebt.
Speicher, ehrlich gemessen
Die interessante Zahl ist nicht VmRSS – ein aus Zygote geforkter Prozess zählt geteilte Framework-Seiten voll mit. Gemessen wird der Unterschied zum leeren Dienst:
| Metrik | Zielwert |
|---|---|
Pss-Delta | ≤ 1.200 KB |
Private_Dirty | ≤ 700 KB |
RssAnon | ≤ 900 KB |
Der native Anteil selbst liegt bei rund 60 KB privat, dazu kommt der dateigestützte Code und der gemappte Trie.