Skip to content

Architecture

pq-cybarg edited this page May 29, 2026 · 1 revision

Architecture

Tetherand splits into three independently shippable subsystems:

  • Tether (milestones M1–M2) β€” the reverse-tether to a laptop.
  • Privacy Chain (M3–M6) β€” composable VPN hops.
  • Threat Detection (M7, M9, M10) β€” on-device monitors and hardened-mode lockdown.

Each subsystem works on its own. You can use the Threat tab without ever enabling the privacy chain; you can use the privacy chain without tethering to a laptop; you can use the tether with no chain and no monitoring.

Process layout

The Android side runs a single process with three foreground services:

dev.tetherand.app (single process)
β”œβ”€ MainActivity            (Compose UI, 4 tabs)
β”œβ”€ TetherandService        (VpnService β€” reverse tether)
β”œβ”€ TetherandChainService   (VpnService β€” privacy chain)
β”œβ”€ ThreatDetectionService  (specialUse FGS β€” monitors)
β”œβ”€ ClipboardScrubberService (specialUse FGS β€” prompt-injection)
β”œβ”€ DecoyListenerService    (specialUse FGS β€” Hardened Mode honeypot)
β”œβ”€ HardenedTileService     (QuickSettings tile)
└─ AoaAccessoryService     (USB-accessory receiver)

Only one VpnService can be active at a time β€” Android enforces this. The user picks which via the bottom-nav tabs.

The host (Mac/Linux) side runs tetherand as a CLI:

tetherand-cli
β”œβ”€ adb reverse                      (USB-ADB transport)
β”œβ”€ tetherand-relay-core             (userspace TCP/UDP/ICMP stack,
β”‚                                    forked from Genymobile/gnirehtet)
β”œβ”€ tetherand-transport-{bt,aoa,tcp} (other transports)
└─ ratatui dashboard                (`tetherand tui`)

Tether data flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€ Seeker (dev.tetherand.app APK) ────────┐
β”‚  TetherandService (VpnService)                  β”‚
β”‚       β”‚ TUN (10.0.0.2)                          β”‚
β”‚       β–Ό                                         β”‚
β”‚  PersistentRelayTunnel ───►  LocalSocket        β”‚
β”‚                              "tetherand"        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                  β”‚ adb reverse
                                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€ Mac (bin/tetherand) ───────────────────┐
β”‚  tetherand-relay-core                          β”‚
β”‚       β”‚  userspace TCP/UDP/ICMP                β”‚
β”‚       β–Ό                                         β”‚
β”‚  Real internet (host Wi-Fi/Ethernet)           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The Seeker's VPN TUN receives every packet the device wants to send. The persistent relay tunnel hands it over adb reverse to a TCP socket on the laptop, where the relay-core (forked from Gnirehtet, Apache-2.0) maintains a userspace TCP/UDP/ICMP stack and forwards through the host's normal network egress.

Bluetooth-RFCOMM, USB-AOA, and LAN-TCP transports plug in at the LocalSocket boundary instead of adb reverse. The data shape is the same β€” raw IP packets delimited by the IPv4 header length field β€” so the relay-core doesn't change.

Privacy chain data flow

The chain orchestrator (dev.tetherand.app.chain.ChainOrchestrator) defines a generic Hop interface:

interface Hop {
    val id: String
    val displayName: String
    val caps: HopCaps
    suspend fun start(input: Channel<ByteArray>): Channel<ByteArray>
    suspend fun stop()
}

Each hop consumes IP packets from its input channel and emits processed IP packets on its output channel. The orchestrator wires them in sequence, so the user-visible chain is just a list of hops:

TUN β†’ WireGuardHop β†’ MullvadHop β†’ NymHop β†’ TorHop β†’ Internet

Each hop also exposes its own capability flags (supportsPQ, supportsMultihop, supportsAntiCensorship), which the UI uses to build the chain-status badge.

The native crypto and protocol logic for each hop lives in a Rust crate at relay/:

Crate Hop What it does
relay/wg WireGuardHop BoringTun WireGuard userspace, JNI'd
relay/tor TorHop arti-client 0.27, JNI'd
relay/nym NymHop nym-sdk gated by with-sdk feature, JNI'd
relay/pt-bridge (auxiliary) obfs4 / meek / webtunnel binary, spawned by Arti's PT manager

The chain orchestrator never sees the underlying crypto; it only sees IP packets in and IP packets out.

Threat detection data flow

The threat-detection foreground service runs five collectors concurrently:

CellInfoSource ───┐
LocationSource ────
WifiScanner   ───┼───► Heuristic orchestrator ───► ThreatDb (Room)
BluetoothScan ────                                       β”‚
AppAudit      β”€β”€β”€β”˜                                       β–Ό
                                                  ThreatScreen

Each collector emits observations into a coroutine flow. The heuristic orchestrator zips observations across collectors when a heuristic needs cross-source data (for example, "TAC change without motion" needs cell observations and accelerometer observations on the same timeline). Verdicts get written to a Room-backed ThreatDb which the Threat tab observes via Compose collectAsState.

The per-location baseline lives in the same database, keyed by a six-character geohash. Baseline writes happen automatically as observations arrive; baseline reads happen when a heuristic needs to know "what's normal for this geohash". No network calls.

On-device file layout

/data/data/dev.tetherand.app/
β”œβ”€ files/
β”‚  β”œβ”€ arti/                        # Arti's cache + state directory
β”‚  β”œβ”€ nym/                         # Nym SDK state
β”‚  └─ databases/
β”‚     β”œβ”€ threats.db                # Room-backed alert feed + baseline
β”‚     └─ ...
β”œβ”€ cache/
β”‚  β”œβ”€ arti/                        # arti cache subdir
β”‚  └─ pts/                         # extracted PT binaries (chmod +x)
└─ shared_prefs/
   β”œβ”€ tetherand-hardened.xml       # EncryptedSharedPreferences
   β”œβ”€ tetherand-nym.xml
   β”œβ”€ tetherand-tor-bridges.xml
   β”œβ”€ tetherand-voiceprint.xml
   └─ ...

The encrypted SharedPreferences files use the AndroidX security library: AES256-SIV for key encryption, AES256-GCM for value encryption, keys held in the AndroidKeyStore (hardware-backed where available).

Subsystem boundaries

The licensing converges towards GPLv3 from the moment M7a links in (NetMonster reflection and the AIMSICD port). M1 and M2's tether code is Apache-2.0 in isolation, but the shipped APK ends up GPLv3 once the threat module is included. The NOTICE files under each subdirectory document this.

Clone this wiki locally