01

Projektübersicht

NetMonitor ist eine Android-App, die den gesamten Netzwerkverkehr eines Geräts lokal abfängt, protokolliert, analysiert und gezielt blockiert — ganz ohne Root-Rechte. Die technische Grundlage bildet Androids offizielle VpnService-API: NetMonitor meldet sich beim System als lokales VPN an, wodurch jedes IP-Paket, das eine App senden oder empfangen will, zunächst durch die eigene Verarbeitungslogik läuft, bevor es (falls erlaubt) den Weg ins echte Internet nimmt.

Diese Dokumentation beschreibt die Architektur auf Systemebene — nicht Zeile für Zeile, sondern als Landkarte der Bausteine und ihres Zusammenspiels. Zwei Bereiche werden dabei besonders vertieft, wie gewünscht: das VPN-Core-System (Kapitel 3–7) und das modulare Tools-System (Kapitel 8–9), da beide den technisch anspruchsvollsten und zugleich am stärksten erweiterbaren Teil der App darstellen.

Codebasis in Zahlen

BestandteilDateienZeilen (gesamt)Zeilen (ohne Leerzeilen)
Java — Anwendungslogik & UI9915.01513.009
C++ — native VPN-Engine11.3491.215
Java — Unit-/Instrumented-Tests121.313—
Summe Produktivcode (Java + C++)10016.36414.224
ℹ️
Alle Zahlen beziehen sich ausschließlich auf Quellcode (.java, .cpp). Layout-XML (112 Dateien, ca. 3.630 Zeilen) ist bewusst nicht mitgezählt, da sie deklarative UI-Beschreibung statt Programmlogik ist.

Kernmerkmale

Kein Root, keine Cloud

Die komplette Verarbeitung läuft lokal auf dem Gerät über die offizielle VpnService-API. Es existiert kein externer NetMonitor-Server.

Zwei austauschbare Engines

Eine native C++-Engine (Standard) und eine reine Java-Engine (Fallback) implementieren dieselbe Schnittstelle und verhalten sich für Regeln, Log und UI identisch.

Modulares Werkzeug-System

20 Diagnose-Tools sind als unabhängige, selbstregistrierende Module implementiert — neue Tools erfordern keine Änderung an bestehendem Code.

Mehrschichtige Automatisierung

Zeitpläne, Datenquoten, WLAN-gebundene Profile und ein Panic-Modus ergänzen die klassische An/Aus-Blockierung um kontextsensitive Regeln.

02

Architektur

Die App gliedert sich in vier gleichberechtigte UI-Tabs, verwaltet von MainPagerAdapter, sowie einen davon unabhängigen Hintergrunddienst, der die eigentliche Paketverarbeitung übernimmt. Die Tabs sind reine Ansichten — sie starten oder stoppen nichts Kritisches selbst, sondern lesen und schreiben Zustand, der zentral in NetVpnService, BlockManager und der Room-Datenbank gehalten wird.

TabFragment-KlasseAufgabe
MonitorMonitorFragmentLive-Traffic, Terminal-Konsole, 24h-Chart, VPN-Steuerung, Panic-Modus
ToolsToolsFragmentRaster aller registrierten Diagnose-Werkzeuge
NotesNotesFragmentPersistente Freitext-Notizen (eigener Tab seit v1.21.0)
SettingsSettingsFragmentEngine-Wahl, Profile, Berechtigungen, Info & Patch Notes

Paketstruktur

Der Code ist strikt nach fachlicher Verantwortung in Pakete unterteilt, nicht nach technischer Schicht allein:

PaketKlassenVerantwortung
vpn.core9Austauschbare VPN-Engines (Java/Native) und deren Verträge
vpn9Paketverarbeitung, Forwarder, Connection-Tracking
ui.*8+Fragmente, Adapter, Dialoge
block7Regel-Engine, Zeitpläne, Profile, Quoten, WLAN-Bindung
data.*10Room-Datenbank, DAOs, Entities, Repository
tools.*20Ein Unterpaket je Diagnose-Werkzeug
widget / tile4Home-Screen-Widget, Quick-Settings-Tile
pcap3Paketmitschnitt im libpcap-Format

Zentraler Hintergrunddienst

NetVpnService läuft unabhängig vom UI-Lebenszyklus. Er besitzt das TUN-Interface, startet die gewählte Engine über VpnCoreFactory und bleibt aktiv, während die App im Hintergrund ist oder sogar geschlossen wurde.

Bedienung ohne App-Öffnen

Home-Screen-Widget und Quick-Settings-Tile sprechen denselben Dienst über Broadcasts bzw. eine transparente Bridge-Activity an — die App selbst muss dafür nicht sichtbar sein.

03

VPN-Core-System — Grundprinzip

Alles beginnt mit einem virtuellen Netzwerk-Interface, dem sogenannten TUN-Device. Man kann es sich wie eine Poststelle mitten in einem Bürogebäude vorstellen: Statt dass jede Abteilung (jede installierte App) ihre Post direkt zum Briefkasten an der Straße trägt, muss zwingend jeder Brief zuerst durch diese zentrale Poststelle. Dort wird er geöffnet, registriert, bei Bedarf zurückgehalten — und nur wenn er durchgelassen wird, geht er tatsächlich auf die Reise ins Internet.

Technisch richtet NetVpnService beim Start dieses Interface über einen VpnService.Builder ein:

ProtokollTUN-AdresseRouteBedingung
IPv410.0.0.2/320.0.0.0/0immer
IPv6fd00:1:fd00:1:fd00:1:fd00:1/128::/0nur wenn das Gerät eine echte globale IPv6-Adresse besitzt

Die IPv6-Prüfung ist bewusst konservativ: NetVpnService durchsucht alle aktiven Netzwerk-Interfaces nach einer nicht-linklokalen, nicht-Loopback-IPv6-Adresse. Findet sich keine, bleibt der Tunnel reines IPv4 — ein Gerät ohne echte IPv6-Anbindung bekäme sonst einen funktionslosen IPv6-Stack aufgezwungen, der nur Fehler produzieren würde.

Zwei Kategorien von Apps werden von vornherein aus dem Tunnel ausgeschlossen (addDisallowedApplication):

IMMER: NetMonitor selbst — sonst würde die App ihre eigene Internetverbindung durch sich selbst hindurch leiten müssen.
OPTIONAL: Als Bypass markierte Apps (siehe Kapitel 10) — sie laufen komplett am Monitoring vorbei, mit vollem Systemzugriff.

Alle übrigen Apps senden ihre Pakete unbemerkt in dieses TUN-Interface. Von dort liest eine der beiden VPN-Engines (Kapitel 5 und 6) die Rohdaten, entscheidet über Weiterleitung oder Blockierung, und schreibt erlaubte Antwortpakete zurück ins Interface — für die ursprüngliche App sieht das wie eine ganz normale Netzwerkantwort aus.

04

VpnCore-Abstraktion

Das Herzstück der Architektur ist eine bewusst schmal gehaltene Schnittstelle: VpnCore. Sie beschreibt nur fünf Operationen — starten, stoppen, laufend prüfen, Block-Modus live umschalten, Name der Engine — und lässt offen, wie die Paketverarbeitung tatsächlich passiert. Dieses Prinzip heißt in der Softwarearchitektur Strategy Pattern: Der aufrufende Code (NetVpnService) kennt nur den Vertrag, nicht die Implementierung.

MethodeZweck
start(tun, blockMode)Startet die Capture-Schleife auf dem TUN-Deskriptor
stop()Fordert die Schleife zum Beenden auf und wartet auf den Worker-Thread
isRunning()Liefert true, solange die Schleife aktiv ist
setBlockMode(bool)Schaltet den globalen Block sofort um — ohne TUN-Neustart
name()Menschlich lesbarer Engine-Name für UI und Info-Bildschirm ("Java" / "Native")

Erzeugt wird die aktive Engine über VpnCoreFactory, die die Nutzerpräferenz aus VpnCorePrefs ausliest:

SCHRITT 1: Ist die native Engine gewünscht (Standardeinstellung seit v1.20.0), versucht die Factory, NativeVpnCore zu instanziieren.
SCHRITT 2: Konnte die native Bibliothek nicht geladen werden (z. B. auf einer nicht unterstützten CPU-Architektur), fällt die Factory automatisch und stillschweigend auf JavaVpnCore zurück.
SCHRITT 3: Ist Java explizit gewählt, wird direkt JavaVpnCore erzeugt.
✔
Dieser Fallback-Mechanismus ist der Grund, warum ein Absturz der nativen Bibliothek auf einem einzelnen Gerätemodell nie zum kompletten Funktionsausfall der App führt — die Java-Engine ist die dauerhaft gepflegte Referenzimplementierung.
05

Java-Engine

JavaVpnCore ist die historisch erste und am längsten erprobte Implementierung. Sie liest Rohpakete in einem eigenen Thread mit maximaler Thread-Priorität (Thread.MAX_PRIORITY) direkt aus dem Dateideskriptor des TUN-Interfaces — Android behandelt das Interface wie eine ganz normale Datei, aus der gelesen und in die geschrieben wird.

Pro gelesenem Paket durchläuft die Schleife vier Schritte:

1. Tracking: ConnectionTracker.onPacket() ermittelt UID und App-Namen des Absenders und aktualisiert Byte-Zähler.
2. Mitschnitt: Ist eine PCAP-Aufnahme aktiv, wird das Rohpaket zusätzlich an PcapRecorder übergeben.
3. Block-Prüfung: Ist der globale Block-Modus aktiv, endet die Verarbeitung hier — das Paket wird schlicht nicht weitergereicht.
4. Weiterleitung: Andernfalls wird das Paket geparst; UDP geht an UdpForwarder, TCP an TcpForwarder.

Wichtig für das Zusammenspiel mit NetVpnService: JavaVpnCore arbeitet nicht mit dem TUN-Deskriptor allein, sondern mit einem Session-Objekt, das alle vom Dienst verwalteten Abhängigkeiten bündelt (TUN-Interface, Laufzustand, beide Forwarder, der Tracker). Dieses Bündeln verhindert, dass Zustand doppelt gehalten oder bei einem Neustart der Engine vergessen wird.

TCP-Forwarder

Baut für jede neue TCP-Verbindung einen echten Socket über das reale Netzwerk-Interface auf. Der Socket wird vor der Erstellung über VpnService.protect() geschützt — ohne diesen Schritt würde die App eine Routing-Schleife erzeugen, weil der neue Socket sonst selbst wieder durch den eigenen VPN-Tunnel liefe.

UDP-Forwarder

Identifiziert Sitzungen über das Tupel aus Quell- und Ziel-IP/Port. DNS-Anfragen (Port 53) erhalten ein verkürztes Idle-Timeout von 10 statt 60 Sekunden, da DNS-Antworten typischerweise sehr schnell eintreffen oder gar nicht.

06

Native Engine (C++)

NativeVpnCore ist seit Version 1.20.0 die Standard-Engine. Sie lädt über JNI eine kompilierte Shared Library (libnetmonitor_native.so, ca. 1.200 Zeilen C++) und übergibt ihr den rohen TUN-Dateideskriptor. Der native Code betreibt eine eigene select()-basierte Ereignisschleife, komplett unabhängig von der Java-VM — das spart den Overhead von Garbage Collection und Objekt-Allokationen auf dem heißesten Pfad der App.

MerkmalWert
Maximale gleichzeitige UDP-Sitzungen128
Maximale gleichzeitige TCP-Sitzungen128
Adressfamiliengetrennte Session-Strukturen für IPv4 und IPv6 (UdpSession/Udp6Session, TcpSession/Tcp6Session)
I/O-Modellselect()

Die Java-Policy-Bridge — der entscheidende Architekturpunkt

Eine naive native Engine würde nur ja/nein auf Paketebene kennen: durchlassen oder verwerfen. Das würde bedeuten, dass die komplette Regel-Logik (Zeitpläne, Quoten, Bypass, Blacklist/Whitelist) in C++ nachgebaut werden müsste — mit doppeltem Pflegeaufwand und Risiko für Inkonsistenzen zwischen beiden Engines. NetMonitor löst das anders:

1. Für jedes Paket ruft der native Code eine Java-Methode auf einem übergebenen "Sink"-Objekt auf (NativePacketPolicy.handle()), per JNI-Callback.
2. Diese Java-Methode führt exakt dieselbe Prüfung durch wie die Java-Engine: UID/App auflösen, Quota prüfen, BlockManager befragen, Log-Eintrag schreiben.
3. Sie gibt einen Boolean zurück — true = weiterleiten, false = verwerfen — den der native Code direkt umsetzt.

Damit trifft die native Engine keine eigenständige Entscheidung. Sie transportiert Pakete extrem effizient, überlässt die eigentliche Regel-Logik aber vollständig der immer aktuellen Java-Implementierung. Beide Engines verhalten sich dadurch für Regeln und UI identisch — ein Nutzer bemerkt beim Umschalten keinen funktionalen Unterschied, nur einen Unterschied im Durchsatz.

💡
Robustheit der Bridge: Die Callback-Signatur wird zur Laufzeit per Reflection gesucht (onPacket, accept, handle, apply, test — mit boolean- oder void-Rückgabe). Das erlaubt es, den Sink künftig durch ein Lambda, ein anderes Interface oder eine ganz andere Klasse zu ersetzen, ohne den nativen Code anzufassen.
⚠️
Ein Wechsel der Engine wird erst beim nächsten Start des VPN-Dienstes wirksam. Schlägt das Laden der nativen Bibliothek fehl (UnsatisfiedLinkError), springt VpnCoreFactory automatisch auf die Java-Engine zurück, ohne dass der Nutzer eingreifen muss.
07

Connection-Tracking & Hilfssysteme

ConnectionTracker ist das gemeinsame Beobachtungsobjekt, das von beiden Engines gleichermaßen genutzt wird. Für jedes Paket löst er die UID der sendenden App über ConnectivityManager auf, den Paketnamen über PackageManager, und führt Buch über übertragene Bytes getrennt nach IPv4/IPv6 und Richtung. Verbindungen werden in einer LinkedHashMap mit einer festen Obergrenze von 500 Einträgen nach LRU-Prinzip gehalten — die am längsten inaktive Verbindung fällt zuerst heraus, sodass der Speicherbedarf auch bei sehr aktiven Geräten begrenzt bleibt.

KlasseAufgabe
IpPacketMinimaler Parser für IPv4-/IPv6-Header (Version, Protokoll, Quelle/Ziel, Header-Länge) — bewusst ohne Unterstützung für IPv6-Extension-Header, da diese im mobilen Alltag praktisch nicht vorkommen
PacketBuilderKonstruiert gültige IP-Antwortpakete inklusive korrekter Prüfsummen für den Rückweg ins TUN-Interface
HostnameResolverErmittelt Klartext-Hostnamen zu einer IP durch Mitlesen von DNS-Antworten und TLS-SNI — funktioniert für IPv4 und IPv6 gleichermaßen
SocketProtectorSchnittstelle, über die nativer Code VpnService.protect() aufrufen kann, um Routing-Schleifen bei eigenen Sockets zu vermeiden
PanicModeZustandsautomat für den zeitlich begrenzten Vollblock (siehe Kapitel 11) — technisch ein temporäres Overlay über dem globalen Block-Flag

Diese Klassen sind bewusst zustandsarm und ohne Android-spezifische Abhängigkeiten geschrieben, wo immer möglich (IpPacket, PacketBuilder) — das macht sie ohne Emulator direkt mit Unit-Tests prüfbar (siehe Kapitel 17).

08

Modulares Tools-System — Architektur

Der Tools-Tab zeigt 20 unabhängige Diagnose-Werkzeuge in einem Kachel-Raster. Der bemerkenswerte Teil ist nicht die Anzahl, sondern wie leicht sich diese Zahl erhöhen lässt: ein neues Werkzeug erfordert keine einzige Codezeile Änderung an bestehenden Klassen. Das ist kein Zufall, sondern das Ergebnis einer bewussten Entwurfsentscheidung, die seit Version 1.19.0 gilt.

Das Vertrags-Interface

Jedes Werkzeug implementiert die schmale Schnittstelle ToolDefinition mit genau vier Elementen:

ElementBedeutung
id()Stabiler, kleingeschriebener Bezeichner (für spätere Deep-Links / Einstellungen)
label()Kurzer Anzeigename auf der Kachel (Empfehlung: höchstens 8 Zeichen)
iconRes()Drawable-Ressource für das Kachel-Icon
launch(Fragment host)Wird beim Antippen aufgerufen — das Werkzeug zeigt seine komplette UI selbst an

Selbstregistrierung statt Eintragsliste

Klassische Plugin-Systeme verlangen oft eine zentrale Liste ("registriere dein Tool hier in dieser Datei"). NetMonitor verzichtet bewusst darauf und nutzt stattdessen Javas eingebauten ServiceLoader-Mechanismus. ToolsFragment fragt beim Öffnen des Tabs schlicht: "Gib mir alle Implementierungen von ToolDefinition, die es gibt" — ohne zu wissen, wie viele das sind oder wie sie heißen:

// ToolsFragment.onViewCreated() — vereinfacht List<ToolDefinition> tools = new ArrayList<>(); for (ToolDefinition t : ServiceLoader.load(ToolDefinition.class)) { tools.add(t); }

Die Zuordnung, welche Klassen dabei gefunden werden, geschieht über die Java-Standardkonvention einer Registrierungsdatei unter META-INF/services/, benannt nach dem vollqualifizierten Interface-Namen. Diese Datei ist die einzige Stelle, die beim Hinzufügen eines Tools berührt wird — und selbst das ist laut Projektnotizen als reiner Build-Schritt gedacht, nicht als manuelle Code-Änderung an einer bestehenden Klasse.

Die Schablone: TemplateTool.java

Um die Einstiegshürde für ein neues Werkzeug zu minimieren, liegt im Tools-Paket eine fertige Kopiervorlage, TemplateTool.java. Sie ist keine Dokumentation über den Prozess, sondern lauffähiger (wenn auch funktionsloser) Code mit klar markierten TODO-Kommentaren an jeder Stelle, die angepasst werden muss.

Der Ablauf zum Hinzufügen eines neuen Werkzeugs reduziert sich damit auf sechs mechanische Schritte:

1. Neues Unterpaket anlegen: tools/<name>/
2. TemplateTool.java dorthin kopieren und in <Name>Tool.java umbenennen
3. Package-Deklaration, Klassenname, id(), label(), iconRes() anpassen
4. Die eigentliche Logik als private Methoden unterhalb von launch() ergänzen
5. Ein passendes Dialog-Layout und ein Icon-Drawable anlegen
6. Fertig — das Werkzeug erscheint automatisch im Tools-Raster

Jedes Werkzeug ist dabei vollständig in sich geschlossen: eigenes UI-Layout, eigene Netzwerklogik, eigener Hintergrund-Thread über Executors.newSingleThreadExecutor(), um die UI während länger laufender Operationen (z. B. Traceroute) nicht zu blockieren. Ergebnisse werden über NetMonitorDialogs (Kapitel 13) in einem einheitlich gestalteten Dialog mit Teilen-Funktion präsentiert — jedes Werkzeug muss dieses Dialog-Grundgerüst nicht selbst nachbauen.

✔
Dieses Muster erklärt, warum die Anzahl der Werkzeuge im Versionsverlauf so gleichmäßig gewachsen ist (im Schnitt ein bis zwei neue Tools pro Nebenversion) — der Grenzaufwand für ein zusätzliches Werkzeug ist konstant klein und unabhängig davon, wie viele bereits existieren.
09

Werkzeug-Katalog

Alle 20 aktuell registrierten Werkzeuge im Überblick, gruppiert nach fachlichem Zweck:

Adressen & Erreichbarkeit

ToolFunktion
SubnetBerechnet aus IP + Maske Netzadresse, Broadcast, nutzbare Hosts und Wildcard-Maske
PingErreichbarkeitstest per TCP-Connect (ICMP ist ohne Root auf Android nicht verfügbar)
WoLSendet ein Wake-on-LAN Magic Packet per UDP-Broadcast
RouteZeigt aktives Netzwerk-Interface, IP-Adressen, Routing-Tabelle, DNS-Server und MTU
TraceTraceroute mit ansteigendem TTL inklusive grober Geo-Zuordnung der Hops

Namensauflösung

ToolFunktion
DNSVorwärts- (A/AAAA) und Rückwärtsauflösung mit Latenzmessung
DNS-msVergleicht die Antwortzeit mehrerer Resolver (Google, Cloudflare, Quad9, OpenDNS, System-DNS)
WHOISRohe WHOIS-Abfrage gegen den zuständigen Server
mDNSLauscht auf Bonjour-/mDNS-Ankündigungen im lokalen Netz

Sicherheit & Diagnose

ToolFunktion
LeakPrüft heuristisch auf DNS- und IPv6-Lecks außerhalb des VPN-Tunnels
TLSFührt einen TLS-Handshake mit SNI durch und zeigt Zertifikatsdetails, Protokoll und Cipher-Suite
HTTPHTTP-GET mit vollständigen Antwort-Headern und Redirect-Kette
PortsTCP-Connect-Scan über einen Port-Bereich, parallel mit Timeout
ListenLiest /proc/net/tcp und /proc/net/udp und zeigt lokal lauschende Dienste mit UID-Zuordnung

Netzwerk-Umgebung

ToolFunktion
DiscoverScannt das lokale Subnetz nach antwortenden Hosts
NeighLiest die ARP-/Neighbor-Tabelle des Kernels
Wi-FiListet sichtbare Access Points mit Kanal, Frequenzband und Signalstärke
CellZeigt Mobilfunkzelle, Standard und Signalwerte, optional mit periodischem Log

Leistung & System

ToolFunktion
SpeedLädt eine Testdatei herunter und berechnet Peak- und Durchschnittsbandbreite
UsageLiest die systemseitige App-Datennutzung über NetworkStatsManager (benötigt separat erteilte Berechtigung)
ℹ️
Die frühere Notizfunktion war ursprünglich ebenfalls ein Tool in diesem Raster. Seit v1.21.0 ist "Notes" ein eigenständiger Tab (Kapitel 13.4); der frei gewordene Rasterplatz wurde durch das neue Route-Tool besetzt — ein direktes Beispiel dafür, wie einfach sich das Raster durch das modulare System verändern lässt.
10

Block-Engine

BlockManager ist ein zustandshaltendes Singleton, das auf jedes einzelne Paket angewendet wird — der sogenannte Hotpath der App. Aus diesem Grund ist er komplett lock-frei über ConcurrentHashMap implementiert: Ein klassisches synchronized hätte bei hoher Paketrate zum Flaschenhals werden können.

Regel-Modell

Jede App-Regel (AppRule) besteht aus vier unabhängigen booleschen Feldern:

FeldBedeutung
blockOutAusgehender Verkehr dieser App wird verworfen
blockInEingehender Verkehr dieser App wird verworfen
bypassApp komplett aus VPN, Monitoring und Blockierung ausgeschlossen
allowedApp steht auf der Erlaubnisliste (nur im Whitelist-Modus relevant)

Auswertungsreihenfolge

Die Methode directionBlockedKey() ist die zentrale Entscheidungsfunktion und prüft in einer festen Priorität:

1. Bypass — hat Vorrang vor allem anderen, blockiert nie.
2. Tages-Quota — überschritten? Dann sofortiger Block, unabhängig vom Modus.
3. Zeitplan — aktives Blockfenster erzwingt vollständigen Block in beide Richtungen.
4. Modus-Logik — im Whitelist-Modus: blockiert, außer explizit erlaubt. Im Blacklist-Modus: erlaubt, außer die passende Richtung ist explizit gesperrt.

Unbekannte Verbindungen (weder Paketname noch UID auflösbar) werden über den Schlüssel "unknown" behandelt und im Whitelist-Modus sicherheitshalber grundsätzlich blockiert.

Zeitpläne (BlockSchedule)

Ein Zeitplan beschreibt ein Wochentag-Bitmuster (Bit 0 = Montag … Bit 6 = Sonntag) sowie Start- und Endzeit in Minuten seit Mitternacht. Die Prüfung behandelt Fenster, deren Ende rechnerisch vor dem Start liegt, als Übernacht-Fenster (z. B. 22:00–06:00 Uhr) und kehrt die Bedingung dafür logisch um — eine Zeit ist dann aktiv, wenn sie nach dem Start oder vor dem Ende liegt, statt wie im Normalfall beides gleichzeitig verlangen zu müssen.

ℹ️
Zeitpläne wirken immer als vollständiger Block (beide Richtungen), unabhängig von individuellen Out/In-Einstellungen — sie sind als harte Auszeit gedacht, nicht als feingranulare Regel.
11

Profile & Automatisierung

Oberhalb der reinen Regel-Engine sitzen mehrere Automatisierungsschichten, die seit Version 1.19 sukzessive dazugekommen sind.

Profile (ProfileManager)

Ein Profil bündelt einen Namen, einen Modus (Blacklist/Whitelist) und eine Menge persistenter Regeln. ProfileManager übernimmt Anlegen, Löschen, Zurücksetzen und — entscheidend — das Aktivieren: Beim Wechsel lädt er alle Regeln des Profils aus der Datenbank und schreibt sie geschlossen in den BlockManager. Legacy-Regeln aus einer älteren Datenbankstruktur (vor Einführung der Profile) werden beim ersten Start automatisch einmalig ins Default-Profil migriert.

WLAN-gebundene Profile (SsidProfileStore / SsidProfileWatcher)

Ein Profil lässt sich fest mit dem Namen (SSID) eines WLAN-Netzwerks verknüpfen — eine 1:1-Zuordnung, ein Netzwerk kann jeweils nur an ein Profil gebunden sein. SsidProfileWatcher registriert sich beim Start des VPN-Dienstes als Beobachter für Netzwerkwechsel und prüft bei jedem Wechsel des Standard-Netzwerks, ob die aktuell verbundene SSID an ein Profil gebunden ist. Ist das der Fall und unterscheidet es sich vom aktuell aktiven Profil, wird automatisch umgeschaltet — praktisch für unterschiedliche Regeln zuhause, im Büro oder in öffentlichen Netzen. Das Auslesen der SSID setzt aus Android-Systemgründen die Standort-Berechtigung voraus.

Tages-Datenquota (AppQuotaStore / AppQuotaLedger)

Unabhängig von Out/In-Blockregeln lässt sich pro App ein tägliches Datenlimit in Megabyte setzen. AppQuotaLedger ist dabei bewusst als reine, Android-freie Rechenlogik ausgelagert (kein Zugriff auf Context oder Systemdienste) — das macht sie einfach unit-testbar. Der Tageswechsel wird nicht über einen Alarm oder Hintergrundjob erkannt, sondern beiläufig bei jedem Zugriff: Ein Vergleich des gespeicherten Datums mit dem heutigen Datum (roll()) setzt den Zähler bei Bedarf automatisch zurück, ohne dass ein Wecker die App aufwecken müsste.

Panic-Modus (PanicMode)

Der Panic-Modus ist ein zeitlich begrenztes Overlay über dem globalen Block: Er merkt sich beim Start den bisherigen Block-Zustand, aktiviert den Vollblock für exakt 10 Minuten und stellt danach automatisch den vorherigen Zustand wieder her. Ein Handler-basierter Sekundentakt aktualisiert währenddessen die Restzeit-Anzeige in Konsole, Kachel-Untertitel und Schnelleinstellungen-Panel. Wird der Block-Modus während einer aktiven Panic-Phase manuell deaktiviert (z. B. über das Widget), verwirft onBlockDisabled() den Timer sofort, statt ihn im Hintergrund weiterlaufen zu lassen.

Zugriffspunkte für Panic

Panic lässt sich an drei Stellen auslösen: per langem Druck auf den Block-Schalter im Monitor-Tab, über das Panel der Schnelleinstellungen-Kachel, oder — sobald aktiv — jederzeit vorzeitig über denselben Schalter beenden.

Zusammenspiel der Schichten

Profile, WLAN-Bindung, Quota, Zeitpläne und Panic wirken alle auf denselben BlockManager ein. Es gibt keine konkurrierenden Entscheidungsinstanzen — jede Schicht verändert lediglich, welche Regeln im Manager aktiv sind oder überschreibt kurzzeitig den globalen Block.

12

Datenbank & Repository

Alles, was einen App-Neustart überdauern muss, liegt in einer Room-Datenbank (AppDatabase), die in sechs Tabellen unterteilt ist:

TabelleEntityInhalt
log_eventsLogEventEntityEinzelne Verbindungsereignisse (App, Protokoll, Richtung, Zielhost)
block_rulesBlockRuleEntityLegacy-Regeln vor Einführung der Profile — nur noch für die einmalige Migration relevant
profilesProfileEntityProfilname und Modus (0 = Blacklist, 1 = Whitelist)
profile_rulesProfileRuleEntityPer-App-Regeln, zusammengesetzter Schlüssel aus Profil-ID und Paketname
schedulesScheduleEntityZeitgesteuerte Blockfenster je App
traffic_samplesTrafficSampleEntityMinutengenaue Traffic-Schnappschüsse für das 24h-Diagramm

RuleRepository ist die einzige Klasse, die sowohl auf die Datenbank als auch auf den In-Memory-BlockManager schreibt — sie hält beide synchron, sodass eine Regeländerung sofort im Hotpath wirkt und dauerhaft gespeichert wird. LogWriter arbeitet dagegen bewusst nicht-blockierend: Ereignisse werden in eine Warteschlange gelegt und asynchron auf einem Hintergrund-Thread nach Room geschrieben, damit das Protokollieren niemals die Paketverarbeitung selbst verzögert.

TrafficSampler wird alle 60 Sekunden vom VPN-Dienst getriggert, summiert die aktuellen Byte-Zähler und schreibt einen Schnappschuss — Einträge älter als 24 Stunden werden dabei automatisch entfernt, sodass die Tabelle dauerhaft klein und die Diagramm-Abfrage konstant schnell bleibt. LogExporter liest bei Bedarf das komplette Protokoll asynchron aus und schreibt es als CSV oder JSON in eine temporäre Datei, die über den System-Share-Dialog geteilt wird.

13

UI-Struktur

Monitor-Fragment

MonitorFragment ist mit weitem Abstand die umfangreichste UI-Klasse. Sie vereint Terminal-Konsole, App-Kachel-Listen (Outbound/Inbound getrennt), das 24h-Diagramm, eine umschaltbare Log-Ansicht und die komplette Werkzeugleiste (Start, Stopp, Löschen, Aufnahme, Block-Schalter mit Panic-Long-Press). Die Trennung zwischen Kachel- und Datenaufbereitung wurde bewusst ausgelagert:

KlasseAufgabe
AppGroupUI-Modell für eine Kachel — aggregiert Verbindungen und Status-Badges einer App
AppGroupFactoryBaut AppGroup-Objekte aus rohen Verbindungsdaten, wertet dabei Bypass/Block/Allow aus und sortiert Bypass-Apps an den Anfang
AppTileAdapterRecyclerView-Adapter für das Kachel-Raster
ConsoleRendererBaut den ASCII-Rahmen der Terminal-Konsole — feste Zeichenbreite, nur reines ASCII, damit die Ausrichtung geräte- und schriftartunabhängig stabil bleibt
TrafficChartViewCustom View für das 24h-Diagramm, berechnet Deltas zwischen Schnappschüssen und ignoriert negative Sprünge nach einem Reset

Dialog-System

Damit nicht jedes der 20 Werkzeuge und jeder Einstellungsdialog sein eigenes Erscheinungsbild erfindet, existiert eine gemeinsame Fabrik: NetMonitorDialogs stellt fertige Bausteine für Bestätigungsdialoge und Formular-Dialoge (Titel, X-Schließen-Symbol, einheitlicher Abstand, optionale Teilen-Funktion) bereit. DialogStyles vereinheitlicht zusätzlich das Erscheinungsbild der Buttons über alle Dialoge hinweg. AppDetailDialog — der komplexeste Einzeldialog der App — nutzt diese Bausteine, um Block-Schalter, Tages-Quota, Zeitpläne (ScheduleAdapter), Nutzungsstatistik und PCAP-Aufnahme für eine einzelne App in einem konsistenten Fenster zu vereinen.

Notes-Fragment

Seit v1.21.0 ein eigener Tab statt eines Werkzeugs. Die Implementierung ist bewusst minimal: ein einzelnes EditText-Feld, dessen Inhalt in SharedPreferences gesichert wird — sowohl explizit über einen Speichern-Button als auch automatisch beim Verlassen des Tabs (onPause).

14

PCAP-Aufnahme

Sowohl die Java- als auch die native Engine reichen jedes verarbeitete Rohpaket zusätzlich an PcapRecorder weiter, sofern eine Aufnahme läuft — unabhängig von der gewählten Engine entsteht so ein identisches Mitschnitt-Format.

KlasseAufgabe
PcapCaptureLauncherStartet bei Bedarf zunächst den VPN-Dienst selbst, bevor die eigentliche Aufnahme beginnt — wird sowohl von der Monitor-Werkzeugleiste (gesamter Verkehr) als auch vom App-Detail-Dialog (eine einzelne App) genutzt
PcapRecorderSammelt Pakete für eine feste Dauer (Standard 60 Sekunden), gefiltert optional auf eine UID/App
PcapWriterSchreibt das klassische libpcap-Format (Mikrosekunden-Zeitstempel, Linktyp DLT_RAW/101 für rohe IP-Pakete) — öffnet direkt und ohne Konvertierung in Wireshark

Ist die Zielapp zum Zeitpunkt der Aufnahme eigentlich blockiert, hebt der Recorder die Blockierung für die Dauer der Aufnahme temporär auf und stellt sie danach automatisch wieder her — sonst gäbe es schlicht nichts aufzuzeichnen. Nach Ablauf der Aufnahmedauer öffnet sich automatisch der System-Share-Dialog.

15

Widget & Quick-Settings-Tile

Home-Screen-Widget

NetMonitorAppWidget zeigt Netz-Typ, IPv4/IPv6-Adresse und Übertragungsrate, aktualisiert über WidgetStatusStore (eine reine SharedPreferences-Brücke zwischen VPN-Dienst und Widget). Vier Tasten senden Broadcasts für Start, Stopp, Pause und Aufnahme. Da der Start einer VPN-Verbindung eine Nutzerbestätigung über eine Activity verlangt, leitet WidgetVpnBridgeActivity — eine unsichtbare Übergangs-Activity — diese Anfrage transparent weiter.

Quick-Settings-Tile

VpnTileService verhält sich kontextabhängig: Ist der Dienst aus, startet ein Tipp ihn direkt über die Bridge-Activity. Läuft er bereits, öffnet ein Tipp stattdessen ein kompaktes Panel mit drei Aktionen — Panic, Pause/Fortsetzen, Stopp. Der Kachel-Untertitel (ab Android 10) zeigt jederzeit den aktuellen Zustand: Off, Forward, Block oder Panic mm:ss.

16

Hilfsklassen & Sound-Feedback

Geiger-Sound (GeigerClick)

Eine bewusst spielerische UX-Funktion: Jedes N-te verarbeitete Paket löst einen kurzen Ton aus — einen hellen Klick für weitergeleiteten, einen dumpfen Ton für blockierten Verkehr. Seit v1.20.1 lassen sich beide Lautstärken getrennt regeln, seit v1.18.11 unterscheiden sich die beiden Klangfarben überhaupt erst. Ein Mindestabstand von 25 ms zwischen zwei Tönen verhindert eine Überlastung des Audiosystems bei hoher Paketrate. Die Dichte ist konfigurierbar: jedes Paket oder nur jedes 20. (Standard).

Weitere Hilfsklassen

KlasseAufgabe
FormatUtilsEinheitliche menschenlesbare Formatierung von Byte-Mengen und Übertragungsraten — von jeder UI-Stelle wiederverwendet, damit "12,3 MB" überall gleich aussieht
SpeedTrackerReine Rechenlogik zur Ermittlung von Auf-/Abwärtsrate aus kumulativen Byte-Zählern — ohne Android-Abhängigkeit, seit v1.19.2 aus MonitorFragment herausgelöst und eigenständig unit-getestet
NetworkStatusHelperMomentaufnahme des aktuellen Netzwerks: Typ, Anbieter-Label, lokale IP-Adressen
LinkInfoAktueller Verbindungstyp und geschätzte Bandbreite für die Konsolen-Anzeige
ChecksumUtilsBerechnung von IP-/Transport-Prüfsummen für selbst konstruierte Pakete
TextShareTeilt beliebigen Klartext über das System-Sheet — von jedem Werkzeug für seine Ergebnis-Ausgabe genutzt
ThemePrefsPersistiert die Theme-Wahl (System/Hell/Dunkel) und wendet sie beim App-Start an
17

Tests & Qualitätssicherung

Die Testsuite umfasst 123 Testfälle über 10 fachliche Testklassen (plus zwei generierte Beispielklassen aus dem Android-Projekt-Template). Bemerkenswert ist, welche Klassen getestet werden: durchweg jene, die bewusst ohne Android-Abhängigkeiten geschrieben wurden und sich dadurch als reine JVM-Unit-Tests ausführen lassen — schnell, ohne Emulator, ohne Gerät.

TestklassePrüft
BlockManagerTestBlacklist-Grundlogik: Out/In-Blockierung, Bypass-Vorrang
BlockManagerWhitelistTestWhitelist-Modus, insbesondere das sichere Default-Verhalten für unbekannte Apps
BlockScheduleTestZeitfenster-Logik, einschließlich Übernacht-Fenster über Mitternacht hinweg
AppQuotaStoreTestTages-Rollover und Grenzwert-Verhalten der Datenquota
IpPacketTestKorrektes Parsen von IPv4-/IPv6-Headern
ChecksumUtilsTestKorrektheit berechneter Prüfsummen
SubnetCalculatorTestCIDR-/Subnetz-Berechnungen des Subnet-Tools
SpeedTrackerTestRaten-Berechnung aus kumulativen Byte-Zählern
FormatUtilsTestByte-/Raten-Formatierung über Größenordnungen hinweg
ConsoleRendererTestStabile ASCII-Ausrichtung der Terminal-Konsole
✔
Das durchgängige Muster — Rechenlogik von Android-APIs trennen (AppQuotaLedger vs. AppQuotaStore, SpeedTracker vs. MonitorFragment) — ist der Hauptgrund, warum trotz der Größe des Projekts ein so hoher Anteil kritischer Logik ohne Emulator geprüft werden kann.